索引失效的情况
- 索引列上使用了函数,导致索引失效
- 对索引使用左或者左右模糊匹配
- 对索引列进行表达式计算
- 对索引隐式类别转换:如用整数查询字符串类型的字段,会导致索引失效,但是用字符串查询整数类型的字段,不会导致索引失效,因为mysql在遇到字符串和数字比较的时候,会自动将字符串转化为数字。
- 联合索引非最左匹配
- Where子句中的OR条件,如果OR条件中有一个条件列没有索引,那么即使其他条件列有索引,也不会使用索引。
mybatis和mybatis-plus的区别
- 功能上的区别:mybatis-plus是mybatis的增强版,提供了很多实用的功能,例如自动生成代码,分页查询,逻辑删除等。
- 语法:MyBatis-plus在MyBatis的基础上做了一些语法上的改进,如使用Lambda表达式进行条件查询,使用Wrapper进行条件构造等。
- 易用性:MyBatis-plus提供了一些简化开发的API,如Wrapper,QueryWrapper,UpdateWrapper等,使用起来更加方便。
- 扩展性:MyBatis-plus提供了一些插件,如分页插件,性能分析插件等,可以方便的扩展MyBatis的功能。
数据库的三范式
- 第一范式:每一列都是不可分割的原子数据项
- 第二范式:表必须有主键,非主键列必须完全依赖于主键,而不能只依赖与主键的一部分
- 第三范式:非主键列必须直接依赖于主键,不能存在传递依赖
编程过程中如何避免空指针异常
- 使用前先检测对象是否为空
- 使用try-catch语句捕获异常
- 使用Objects类的isNull()方法判断对象是否为空
- 使用Optional类封装可能为null的对象
- 使用代码检查工具和单元测试工具
线程安全的概念
在多线程环境下,当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
用锁会有什么问题
- 死锁:两个或者多个线程互相持有对方所需要的锁,导致线程无法继续执行。
- 降低性能:加锁会导致线程阻塞,降低程序的性能。
lunix命令:创建一个新文件
touch filename
线程和进程的区别
线程是进程的一部分,进程是资源分配的最小单位,线程是CPU调度的最小单位。
每个进程都有自己独立的一块内存空间,而线程共享进程的内存空间。一个进程可以启动多个线程,每个线程并行执行不同的任务。线程的崩溃会导致整个进程的崩溃,但是进程的崩溃不会导致其他进程的崩溃。
主线程等待子线程执行完毕的方法
- 使用join方法
- 使用CountDownLatch
- 使用CyclicBarrier
- 使用FutureTask
- 使用Condition
- 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中的元素去重
- 利用TreeSet的元素不重复的特性
public static List<String> delRepeat(List<String> list) {
List<String> result = new ArrayList<>(new TreeSet<>(list));
return result;
}
- 利用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;
}
- 利用LinkedHashSet的元素不重复的特性
public static List<String> delRepeat1(List<String> list) {
List<String> listNew2 = new ArrayList<String>(new LinkedHashSet<String>(list));
return listNew2;
}
- 利用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;
}
- 利用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内存淘汰机制
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
- lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)。这当中有两种类型,一种是在过期的键值当中删除,一种是在使用的键值当中删除
- lfu:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)。这当中有两种类型,一种是在过期的键值当中删除,一种是在使用的键值当中删除
- 随机删除某个key,也有两种类型,一种是在过期的键值当中删除,一种是在全部的键值当中删除
- ttl:当内存不足以容纳新写入数据时,在键空间中,移除最早过期的key
redis的持久化
- RDB:在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读到内存里。
- AOF:在指定的时间间隔内将执行的写命令追加到AOF文件中,恢复时通过重新执行AOF文件中的命令来恢复原始的数据。
Springboot热启动
- 在pom.xml中引入spring-boot-devtools依赖
- 在IDEA中设置自动编译,过程是Settings -> Build,Execution,Deployment -> Compiler -> Build Project automatically
- shift+ctrl+alt+/,选择Registry,勾选compiler.automake.allow.when.app.running
锁和监视器
- 锁是用来保护共享资源的,当多个线程同时访问共享资源时,需要使用锁来保证共享资源的正确性。
- 监视器是一种同步机制,它可以保证在同一时刻,只有一个线程可以执行某个方法或者某个代码块,主要是通过对象的wait()和notify()方法实现的。
事务隔离级别,事务传播行为
事务隔离级别:未提交读、已提交读、可重复读、可串行化
MySQL事务传播行为是指事务在数据库中的传播方式,即事务的开始和结束的条件。MySQL支持多种事务传播行为,包括以下几种:
REQUIRED(默认):如果当前没有事务,就新建一个事务;如果已经存在一个事务中,就加入到这个事务中。
SUPPORTS:如果当前没有事务,就以非事务方式执行;如果当前存在事务,就加入到这个事务中。
MANDATORY:如果当前没有事务,就抛出异常;如果当前存在事务,就加入到这个事务中。
REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务,并在新的事务中执行。
NOT_SUPPORTED:无论当前是否存在事务,都以非事务方式执行。
NEVER:如果当前存在事务,则抛出异常。
NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则新建一个事务。
需要注意的是,以上的事务传播行为只在使用事务的方法上有效,对于没有使用事务的方法,事务传播行为不起作用。
资源分配在什么地方
- 栈:存放基本类型的变量和对象的引用,由系统自动分配和释放,速度快,但是栈的空间有限。
- 堆:存放对象实例和数组,由程序员分配和释放,速度慢,但是堆的空间比较大。
左连接和右连接
左连接:左表中的所有记录都会被选出来,而右表中的记录只有和左表中的记录匹配的才会被选出来。
右连接:右表中的所有记录都会被选出来,而左表中的记录只有和右表中的记录匹配的才会被选出来。
内连接:只有左表和右表中的记录匹配的才会被选出来。
Redis的数据结构
redis的数据结构有以下几种:
- 字符串:字符串是redis最基本的数据结构,它的底层实现是简单动态字符串,字符串的最大长度为512M。
- 列表:列表是一种有序的字符串列表,它的底层实现是双向链表,列表的最大长度为2^32-1。
- 集合:集合是一种无序的字符串集合,它的底层实现是哈希表,集合的最大长度为2^32-1。
- 有序集合:有序集合是一种有序的字符串集合,它的底层实现是跳跃表和哈希表,有序集合的最大长度为2^32-1。
- 哈希表:哈希表是一种键值对的无序散列表,它的底层实现是哈希表,哈希表的最大长度为2^32-1。
- 地理位置:地理位置是一种存储地理位置信息的数据结构,它的底层实现是二维的GeoHash编码,地理位置的最大长度为2^32-1。
- bitmap:bitmap是一种位图数据结构,它的底层实现是字符串,bitmap的最大长度为2^32-1。
- hyperloglog:hyperloglog是一种基数统计算法,它的底层实现是字符串,hyperloglog的最大长度为2^32-1。
缓存一致性如何保证
- 更新数据库然后再更新缓存:在并发更新数据库场景下,会将脏数据刷到缓存上,导致缓存和数据库不一致。在写请求较多时会出现不一致性问题,读请求较多时可以保证一致性。
- 删除缓存再更新数据库:更新数据库之前,若有查询请求,会将脏数据刷到缓存上,导致缓存和数据库不一致。在读请求较多时会出现不一致性问题,写请求较多时可以保证一致性。
- 延迟双删:在更新数据库之前,先删除缓存,然后延迟一段时间再更新数据库,这样可以保证缓存和数据库的一致性,但是会增加延迟。
缓存击穿,缓存雪崩,缓存穿透
- 缓存击穿:缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
- 缓存穿透:缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
- 缓存雪崩:缓存雪崩是指在某一个时间段,缓存集中过期失效,而查询数据量巨大,引起数据库压力过大甚至宕机。redis故障宕机也可能导致缓存雪崩。
解决方法:
缓解缓存击穿的措施
- 设置热点数据永不过期,或者设置过期时间为一个较长的时间,这样可以避免热点数据过期失效时,大量请求直接访问数据库。
- 加载缓存时加锁,在加载缓存时,可以使用分布式锁等机制,避免多个请求同时访问数据库
- 使用互斥锁:在缓存失效时,可以使用互斥锁等机制,避免多个请求同时访问数据库。
缓解缓存雪崩的措施
- 设置不同的过期时间,将缓存的过期时间设置为不同的时间,避免同时失效导致大量请求直接访问数据库。
- 使用缓存预热,提前加载缓存,避免在缓存失效时,大量请求直接访问数据库。
- 使用多级缓存,将缓存分为多级,例如本地缓存,分布缓存等
- 使用限流策略:在该并发时,可以使用限流策略,例如漏桶算法,立牌算法等,控制请求的流量
- 数据库容灾:在缓存失效时,可以使用数据库容灾机制,例如读写分离,主从复制等,避免数据库宕机。
缓解缓存穿透的措施
参数校验,布隆过滤器,缓存空对象
Java多线程如何实现,实现方式有什么区别
- 继承Thread类
优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可。
缺点:Java不支持多继承,因此如果继承了Thread类,就无法继承其他类。如果需要扩展功能,只能通过内部类或匿名内部类的方式来实现。 - 实现Runnable接口
优点:可以继承其他类,可以实现多个接口。
缺点:如果需要访问当前线程,需要使用Thread.currentThread()方法。代码量较多。 - 实现Callable接口和Future接口
优点:可以获取线程执行结果,支持有返回值的线程。
缺点:相比于实现Runnable接口,代码量较多。 - 使用线程池
优点:可以重复利用线程,减少线程创建和销毁的开销,提高性能。
缺点:如果线程池中的线程数量过多,会导致线程调度开销过大,降低性能。
多线程中的两种锁
synchronized和ReentrantLock
从源层面来看:synchronized是JVM层面的锁,ReentrantLock是JDK层面的锁。
从功能上来看:synchronized是不可中断锁,ReentrantLock是可中断锁。
从锁的获取和释放方式来看:synchronized是隐式锁,ReentrantLock是显式锁。
string和stringbuffer的区别
- string是不可变的,stringbuffer是可变的。每次对string进行操作都会生成一个新的string对象,而stringbuffer则不会。
- 由于string是不可变的,所以是线程安全,stringbuffer虽然是可变的,但是对方法添加了同步锁,所以也是线程安全的。
- 操作少量数据的时候使用string,多线程操作字符串缓冲区下操作大量数据的时候使用stringbuffer。
读取配置文件的变量的注解
@Value(“${key}”)
使用方式:
- 在类上添加@configuration注解,将这个配置对象注入到容器中,哪里需要使用就在哪里注入。通过@AutoWired注解注入就可以获取属性值
- 在对象的属性上添加@Value注解,以${}的形式获取配置文件中的值
@ConfigrationProperties(prefix = “key”)
使用方式:
- 在类上添加@ConfigurationProperties注解,声明当前类为配置读取类
- 在注解的括号中,设置要读取属性的前缀:prefix
- 添加@Configuration注解,将这个配置对象注入到容器中。(或者@Controller、@RestController、@Service、@Componet等注解)
创建线程的方法
- 继承Thread类
- 继承Runnable接口
- 继承Callable接口和Future接口
- 线程池
垃圾回收算法
判断是否需要进行垃圾回收的算法
- 引用计数法
- 可达性分析法
垃圾回收算法 - 标记清除法
- 复制法
- 标记整理法
- 分代收集算法:新生代使用复制算法进行垃圾回收,老年代使用标记整理算法进行垃圾回收。新创建的对象属于是新生代,经过一定次数的垃圾回收仍然存活的对象会被移动到老年代。
垃圾回收器
- Serial收集器:单线程,使用复制算法进行垃圾回收,适用于单核CPU的环境。
- Serial Old收集器:单线程,使用标记整理算法进行垃圾回收,适用于单核CPU的环境。
- parNew收集器:多线程,使用复制算法进行垃圾回收,适用于多核CPU的环境。
- parallel scavenge收集器:多线程,使用复制算法进行垃圾回收,适用于多核CPU的环境。
- Parallel Old收集器:多线程,使用标记整理算法进行垃圾回收,适用于多核CPU的环境。
- CMS收集器:多线程,使用标记清除算法进行垃圾回收,适用于多核CPU的环境。
- G1收集器:多线程,使用标记整理算法+复制算法进行垃圾回收,适用于多核CPU的环境。
JDK9之后,G1收集器成为默认的垃圾回收器。
spring框架
AOP和IOC
Spring是一款轻量级的开源框架,它的核心是IOC和AOP。
list,set和map之间的区别和作用
这三者都是Java中的集合类型,都是用来存储对象的容器。
- list是有序的,可重复的集合,按照元素的添加顺序来存储元素,可以通过索引来访问元素。插入和删除元素时,需要移动元素,所以插入和删除元素的时间复杂度为O(n),查询元素的时间复杂度为O(1)。
- Set是无序的,不可重复的集合,不按照元素的添加顺序来存储元素,不可以通过索引来访问元素。插入和删除元素时,不需要移动元素,所以插入和删除元素的时间复杂度为O(1),查询元素的时间复杂度为O(1)。
- Map是存储键值对的集合,也被称为映射,每个元素包含一个键和一个值,键不可重复,值可以重复。Map中的键是无序的,不可重复的,不可以通过索引来访问元素,但是可以通过键来访问值。插入和删除元素时,不需要移动元素,所以插入和删除元素的时间复杂度为O(1),查询元素的时间复杂度为O(1)。
单例模式
单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点来访问该实例。
单例模式的优点:
- 保证了一个类只有一个实例,节省了系统资源。
- 提供了一个全局访问点,方便对该实例进行访问和操作。
单例模式的缺点:
- 单例模式的实现可能会增加代码的复杂度。
- 单例模式的单一实例可能会成为系统性能瓶颈,因为所有的访问都需要通过同一个实例进行。
单例模式的应用场景:
- 系统中只需要一个实例的对象,如线程池、数据库连接池等。
- 需要频繁创建和销毁的对象,如日志对象、对话框等。
- 需要控制资源使用的情况,如计数器、序列号生成器等。
手写一个单例模式连接池
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);
}
}