LOADING

加载过慢请开启缓存 浏览器默认开启

2024泰隆一面

索引失效的情况

  1. 索引列上使用了函数,导致索引失效
  2. 对索引使用左或者左右模糊匹配
  3. 对索引列进行表达式计算
  4. 对索引隐式类别转换:如用整数查询字符串类型的字段,会导致索引失效,但是用字符串查询整数类型的字段,不会导致索引失效,因为mysql在遇到字符串和数字比较的时候,会自动将字符串转化为数字。
  5. 联合索引非最左匹配
  6. Where子句中的OR条件,如果OR条件中有一个条件列没有索引,那么即使其他条件列有索引,也不会使用索引。

mybatis和mybatis-plus的区别

  1. 功能上的区别:mybatis-plus是mybatis的增强版,提供了很多实用的功能,例如自动生成代码,分页查询,逻辑删除等。
  2. 语法:MyBatis-plus在MyBatis的基础上做了一些语法上的改进,如使用Lambda表达式进行条件查询,使用Wrapper进行条件构造等。
  3. 易用性:MyBatis-plus提供了一些简化开发的API,如Wrapper,QueryWrapper,UpdateWrapper等,使用起来更加方便。
  4. 扩展性:MyBatis-plus提供了一些插件,如分页插件,性能分析插件等,可以方便的扩展MyBatis的功能。

数据库的三范式

  1. 第一范式:每一列都是不可分割的原子数据项
  2. 第二范式:表必须有主键,非主键列必须完全依赖于主键,而不能只依赖与主键的一部分
  3. 第三范式:非主键列必须直接依赖于主键,不能存在传递依赖

编程过程中如何避免空指针异常

  1. 使用前先检测对象是否为空
  2. 使用try-catch语句捕获异常
  3. 使用Objects类的isNull()方法判断对象是否为空
  4. 使用Optional类封装可能为null的对象
  5. 使用代码检查工具和单元测试工具

线程安全的概念

在多线程环境下,当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。

用锁会有什么问题

  1. 死锁:两个或者多个线程互相持有对方所需要的锁,导致线程无法继续执行。
  2. 降低性能:加锁会导致线程阻塞,降低程序的性能。

lunix命令:创建一个新文件

touch filename

线程和进程的区别

线程是进程的一部分,进程是资源分配的最小单位,线程是CPU调度的最小单位。
每个进程都有自己独立的一块内存空间,而线程共享进程的内存空间。一个进程可以启动多个线程,每个线程并行执行不同的任务。线程的崩溃会导致整个进程的崩溃,但是进程的崩溃不会导致其他进程的崩溃。

主线程等待子线程执行完毕的方法

  1. 使用join方法
  2. 使用CountDownLatch
  3. 使用CyclicBarrier
  4. 使用FutureTask
  5. 使用Condition
  6. synchronized的等待/通知机制

对Java集合的了解?ArrayList和LinkedList的区别

有两个接口,Collection和Map,Collection接口的实现类有List、Set和Queue,Map接口的实现类有HashMap、TreeMap、LinkedHashMap、HashTable等

两者的区别:
底层数据结构不同:ArrayList底层使用数组实现,LinkedList底层使用双向链表实现。
插入和删除操作:ArrayList插入和删除元素时,需要移动元素,时间复杂度为O(n),而LinkedList插入和删除元素时,不需要移动元素,时间复杂度为O(1)。
随机访问:ArrayList可以通过下标随机访问元素,时间复杂度为O(1),而LinkedList不能通过下标随机访问元素,需要遍历链表,时间复杂度为O(n)。
空间占用:ArrayList的空间占用比LinkedList大,因为ArrayList底层使用数组实现,而数组的长度是固定的,而LinkedList底层使用链表实现,链表的长度是不固定的。
线程安全:ArrayList是线程不安全的,而LinkedList也是线程不安全的。

如何实现对List中的元素去重

  1. 利用TreeSet的元素不重复的特性
public static List<String> delRepeat(List<String> list) {
    List<String> result = new ArrayList<>(new TreeSet<>(list));
    return result;
}
  1. 利用Set的元素不重复的特性
public static List<String> distinct(List<String> list) {
    List doubleList = new ArrayList();
    if(list.size()>0&&list != null){ //判断传入的list是否为空
        Set set = new HashSet(); //新建一个HashSet
        set.addAll(list); //list里的所有东西放入set中 进行去重
        doubleList.addAll(set); //把去重完的set重新放回list里
    }
    return doubleList;
}
  1. 利用LinkedHashSet的元素不重复的特性
 public static List<String> delRepeat1(List<String> list) {
        List<String> listNew2 = new ArrayList<String>(new LinkedHashSet<String>(list));
        return listNew2;
    }
  1. 利用contains方法
public static List<String> delRepeat2(List<String> list) {
    List<String> listNew = new ArrayList<String>();
    for (String str : list) {
        if (!listNew.contains(str)) {
            listNew.add(str);
        }
    }
    return listNew;
}
  1. 利用Java8的stream流
public static List<String> delRepeat3(List<String> list) {
    List<String> listNew = list.stream().distinct().collect(Collectors.toList());
    return listNew;
}

遍历List过程中删除元素会出现什么问题

这种遍历有可能会遗漏某个元素,因为删除元素后List的size在变化,元素的索引也在变化,比如你循环到第2个元素的时候你把它删了,接下来你去访问第3个元素,实际上访问到的是原先的第4个元素

MySQL是否索引越多越好

不是,索引越多,维护索引的成本就越高,索引会占用磁盘空间,会降低写操作的性能,会降低数据库的整体性能。
如果不考虑空间因素的话,也会出现查询时回表的代价,而且索引也不一定有效,白白增加后期维护的成本

大量数据插入Redis中会发生什么

由于Redis的数据是直接存储在内存上的,大量的数据直接插入Redis中,有可能会导致内存溢出而触发Redis的内存淘汰机制,导致数据丢失。

redis内存淘汰机制

  1. noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
  2. lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)。这当中有两种类型,一种是在过期的键值当中删除,一种是在使用的键值当中删除
  3. lfu:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)。这当中有两种类型,一种是在过期的键值当中删除,一种是在使用的键值当中删除
  4. 随机删除某个key,也有两种类型,一种是在过期的键值当中删除,一种是在全部的键值当中删除
  5. ttl:当内存不足以容纳新写入数据时,在键空间中,移除最早过期的key

redis的持久化

  1. RDB:在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读到内存里。
  2. AOF:在指定的时间间隔内将执行的写命令追加到AOF文件中,恢复时通过重新执行AOF文件中的命令来恢复原始的数据。

Springboot热启动

  1. 在pom.xml中引入spring-boot-devtools依赖
  2. 在IDEA中设置自动编译,过程是Settings -> Build,Execution,Deployment -> Compiler -> Build Project automatically
  3. shift+ctrl+alt+/,选择Registry,勾选compiler.automake.allow.when.app.running

锁和监视器

  1. 锁是用来保护共享资源的,当多个线程同时访问共享资源时,需要使用锁来保证共享资源的正确性。
  2. 监视器是一种同步机制,它可以保证在同一时刻,只有一个线程可以执行某个方法或者某个代码块,主要是通过对象的wait()和notify()方法实现的。

事务隔离级别,事务传播行为

事务隔离级别:未提交读、已提交读、可重复读、可串行化

MySQL事务传播行为是指事务在数据库中的传播方式,即事务的开始和结束的条件。MySQL支持多种事务传播行为,包括以下几种:

  1. REQUIRED(默认):如果当前没有事务,就新建一个事务;如果已经存在一个事务中,就加入到这个事务中。

  2. SUPPORTS:如果当前没有事务,就以非事务方式执行;如果当前存在事务,就加入到这个事务中。

  3. MANDATORY:如果当前没有事务,就抛出异常;如果当前存在事务,就加入到这个事务中。

  4. REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务,并在新的事务中执行。

  5. NOT_SUPPORTED:无论当前是否存在事务,都以非事务方式执行。

  6. NEVER:如果当前存在事务,则抛出异常。

  7. NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则新建一个事务。

需要注意的是,以上的事务传播行为只在使用事务的方法上有效,对于没有使用事务的方法,事务传播行为不起作用。

资源分配在什么地方

  1. 栈:存放基本类型的变量和对象的引用,由系统自动分配和释放,速度快,但是栈的空间有限。
  2. 堆:存放对象实例和数组,由程序员分配和释放,速度慢,但是堆的空间比较大。

左连接和右连接

左连接:左表中的所有记录都会被选出来,而右表中的记录只有和左表中的记录匹配的才会被选出来。
右连接:右表中的所有记录都会被选出来,而左表中的记录只有和右表中的记录匹配的才会被选出来。
内连接:只有左表和右表中的记录匹配的才会被选出来。

Redis的数据结构

redis的数据结构有以下几种:

  1. 字符串:字符串是redis最基本的数据结构,它的底层实现是简单动态字符串,字符串的最大长度为512M。
  2. 列表:列表是一种有序的字符串列表,它的底层实现是双向链表,列表的最大长度为2^32-1。
  3. 集合:集合是一种无序的字符串集合,它的底层实现是哈希表,集合的最大长度为2^32-1。
  4. 有序集合:有序集合是一种有序的字符串集合,它的底层实现是跳跃表和哈希表,有序集合的最大长度为2^32-1。
  5. 哈希表:哈希表是一种键值对的无序散列表,它的底层实现是哈希表,哈希表的最大长度为2^32-1。
  6. 地理位置:地理位置是一种存储地理位置信息的数据结构,它的底层实现是二维的GeoHash编码,地理位置的最大长度为2^32-1。
  7. bitmap:bitmap是一种位图数据结构,它的底层实现是字符串,bitmap的最大长度为2^32-1。
  8. hyperloglog:hyperloglog是一种基数统计算法,它的底层实现是字符串,hyperloglog的最大长度为2^32-1。

缓存一致性如何保证

  1. 更新数据库然后再更新缓存:在并发更新数据库场景下,会将脏数据刷到缓存上,导致缓存和数据库不一致。在写请求较多时会出现不一致性问题,读请求较多时可以保证一致性。
  2. 删除缓存再更新数据库:更新数据库之前,若有查询请求,会将脏数据刷到缓存上,导致缓存和数据库不一致。在读请求较多时会出现不一致性问题,写请求较多时可以保证一致性。
  3. 延迟双删:在更新数据库之前,先删除缓存,然后延迟一段时间再更新数据库,这样可以保证缓存和数据库的一致性,但是会增加延迟。

缓存击穿,缓存雪崩,缓存穿透

  1. 缓存击穿:缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
  2. 缓存穿透:缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
  3. 缓存雪崩:缓存雪崩是指在某一个时间段,缓存集中过期失效,而查询数据量巨大,引起数据库压力过大甚至宕机。redis故障宕机也可能导致缓存雪崩。

解决方法:

缓解缓存击穿的措施

  1. 设置热点数据永不过期,或者设置过期时间为一个较长的时间,这样可以避免热点数据过期失效时,大量请求直接访问数据库。
  2. 加载缓存时加锁,在加载缓存时,可以使用分布式锁等机制,避免多个请求同时访问数据库
  3. 使用互斥锁:在缓存失效时,可以使用互斥锁等机制,避免多个请求同时访问数据库。

缓解缓存雪崩的措施

  1. 设置不同的过期时间,将缓存的过期时间设置为不同的时间,避免同时失效导致大量请求直接访问数据库。
  2. 使用缓存预热,提前加载缓存,避免在缓存失效时,大量请求直接访问数据库。
  3. 使用多级缓存,将缓存分为多级,例如本地缓存,分布缓存等
  4. 使用限流策略:在该并发时,可以使用限流策略,例如漏桶算法,立牌算法等,控制请求的流量
  5. 数据库容灾:在缓存失效时,可以使用数据库容灾机制,例如读写分离,主从复制等,避免数据库宕机。

缓解缓存穿透的措施

参数校验,布隆过滤器,缓存空对象

Java多线程如何实现,实现方式有什么区别

  1. 继承Thread类

    优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可。
    缺点:Java不支持多继承,因此如果继承了Thread类,就无法继承其他类。如果需要扩展功能,只能通过内部类或匿名内部类的方式来实现。

  2. 实现Runnable接口

    优点:可以继承其他类,可以实现多个接口。
    缺点:如果需要访问当前线程,需要使用Thread.currentThread()方法。代码量较多。

  3. 实现Callable接口和Future接口

    优点:可以获取线程执行结果,支持有返回值的线程。
    缺点:相比于实现Runnable接口,代码量较多。

  4. 使用线程池

    优点:可以重复利用线程,减少线程创建和销毁的开销,提高性能。
    缺点:如果线程池中的线程数量过多,会导致线程调度开销过大,降低性能。

多线程中的两种锁

synchronized和ReentrantLock
从源层面来看:synchronized是JVM层面的锁,ReentrantLock是JDK层面的锁。
从功能上来看:synchronized是不可中断锁,ReentrantLock是可中断锁。
从锁的获取和释放方式来看:synchronized是隐式锁,ReentrantLock是显式锁。

string和stringbuffer的区别

  1. string是不可变的,stringbuffer是可变的。每次对string进行操作都会生成一个新的string对象,而stringbuffer则不会。
  2. 由于string是不可变的,所以是线程安全,stringbuffer虽然是可变的,但是对方法添加了同步锁,所以也是线程安全的。
  3. 操作少量数据的时候使用string,多线程操作字符串缓冲区下操作大量数据的时候使用stringbuffer。

读取配置文件的变量的注解

@Value(“${key}”)
使用方式:

  1. 在类上添加@configuration注解,将这个配置对象注入到容器中,哪里需要使用就在哪里注入。通过@AutoWired注解注入就可以获取属性值
  2. 在对象的属性上添加@Value注解,以${}的形式获取配置文件中的值

@ConfigrationProperties(prefix = “key”)
使用方式:

  1. 在类上添加@ConfigurationProperties注解,声明当前类为配置读取类
  2. 在注解的括号中,设置要读取属性的前缀:prefix
  3. 添加@Configuration注解,将这个配置对象注入到容器中。(或者@Controller、@RestController、@Service、@Componet等注解)

创建线程的方法

  1. 继承Thread类
  2. 继承Runnable接口
  3. 继承Callable接口和Future接口
  4. 线程池

垃圾回收算法

判断是否需要进行垃圾回收的算法

  1. 引用计数法
  2. 可达性分析法
    垃圾回收算法
  3. 标记清除法
  4. 复制法
  5. 标记整理法
  6. 分代收集算法:新生代使用复制算法进行垃圾回收,老年代使用标记整理算法进行垃圾回收。新创建的对象属于是新生代,经过一定次数的垃圾回收仍然存活的对象会被移动到老年代。

垃圾回收器

  1. Serial收集器:单线程,使用复制算法进行垃圾回收,适用于单核CPU的环境。
  2. Serial Old收集器:单线程,使用标记整理算法进行垃圾回收,适用于单核CPU的环境。
  3. parNew收集器:多线程,使用复制算法进行垃圾回收,适用于多核CPU的环境。
  4. parallel scavenge收集器:多线程,使用复制算法进行垃圾回收,适用于多核CPU的环境。
  5. Parallel Old收集器:多线程,使用标记整理算法进行垃圾回收,适用于多核CPU的环境。
  6. CMS收集器:多线程,使用标记清除算法进行垃圾回收,适用于多核CPU的环境。
  7. G1收集器:多线程,使用标记整理算法+复制算法进行垃圾回收,适用于多核CPU的环境。
    JDK9之后,G1收集器成为默认的垃圾回收器。

spring框架

AOP和IOC
Spring是一款轻量级的开源框架,它的核心是IOC和AOP。

list,set和map之间的区别和作用

这三者都是Java中的集合类型,都是用来存储对象的容器。

  1. list是有序的,可重复的集合,按照元素的添加顺序来存储元素,可以通过索引来访问元素。插入和删除元素时,需要移动元素,所以插入和删除元素的时间复杂度为O(n),查询元素的时间复杂度为O(1)。
  2. Set是无序的,不可重复的集合,不按照元素的添加顺序来存储元素,不可以通过索引来访问元素。插入和删除元素时,不需要移动元素,所以插入和删除元素的时间复杂度为O(1),查询元素的时间复杂度为O(1)。
  3. Map是存储键值对的集合,也被称为映射,每个元素包含一个键和一个值,键不可重复,值可以重复。Map中的键是无序的,不可重复的,不可以通过索引来访问元素,但是可以通过键来访问值。插入和删除元素时,不需要移动元素,所以插入和删除元素的时间复杂度为O(1),查询元素的时间复杂度为O(1)。

单例模式

单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点来访问该实例。

单例模式的优点:

  1. 保证了一个类只有一个实例,节省了系统资源。
  2. 提供了一个全局访问点,方便对该实例进行访问和操作。

单例模式的缺点:

  1. 单例模式的实现可能会增加代码的复杂度。
  2. 单例模式的单一实例可能会成为系统性能瓶颈,因为所有的访问都需要通过同一个实例进行。

单例模式的应用场景:

  1. 系统中只需要一个实例的对象,如线程池、数据库连接池等。
  2. 需要频繁创建和销毁的对象,如日志对象、对话框等。
  3. 需要控制资源使用的情况,如计数器、序列号生成器等。

手写一个单例模式连接池

public class ConnectionPool {
    private static ConnectionPool instance = null;
    private List<Connection> connectionList = new ArrayList<>();
    private ConnectionPool() {
        for (int i = 0; i < 10; i++) {
            connectionList.add(new Connection());
        }
    }
    public static ConnectionPool getInstance() {
        if (instance == null) {
            instance = new ConnectionPool();
        }
        return instance;
    }
    public Connection getConnection() {
        if (connectionList.size() > 0) {
            return connectionList.remove(0);
        }
        return null;
    }
    public void releaseConnection(Connection connection) {
        connectionList.add(connection);
    }
}
本文作者:GWB
当前时间:2023-11-09 11:11:07
版权声明:本文由gwb原创,本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 国际许可协议。
转载请注明出处!