其他总结

1.介绍下Java集合Fail-Fast、Fail-Safe机制

(1)Fail-Fast(快速失败)​​

​​定义​​:

在集合迭代过程中,如果检测到结构被修改(如增删元素),立即抛出 ConcurrentModificationException,终止操作。

​​实现原理​​:

​​modCount 机制​​:集合内部维护一个 modCount(修改计数器),每次结构修改(如 add、remove)时递增。

​​迭代器检查​​:迭代器初始化时记录当前 modCount 为 expectedModCount,每次调用 next() 或 remove() 时检查是否一致。

final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

​​适用集合​​:非线程安全的集合​​:如 ArrayList、HashMap、LinkedList。

​​特点​​:快速发现错误​​:适用于单线程开发环境,帮助开发者尽早发现并发修改问题。​​不保证线程安全​​:仅作为错误检测机制,不解决多线程安全问题。

其他注意点:Iterator.remove() 是迭代器自身的方法,它在删除元素后会​​主动同步 modCount 和 expectedModCount​​,确保两者一致,从而避免触发异常。

​​(2)​​Fail-Safe(安全失败)​​

​​定义​​:

在集合迭代过程中,允许结构修改,但迭代器基于原始数据副本或并发控制机制工作,不会抛出异常。

​​实现原理​​:

​​数据副本​​:迭代器遍历集合的副本(如 CopyOnWriteArrayList),原集合的修改不影响当前迭代。

​​并发控制​​:使用线程安全结构(如 ConcurrentHashMap 的分段锁),保证迭代过程中操作的安全性。

​​适用集合​​:

​​线程安全的并发集合​​:如 CopyOnWriteArrayList、ConcurrentHashMap。

List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b"));

Iterator<String> it = list.iterator();

list.add("c"); // 修改原集合

it.forEachRemaining(System.out::println); // 输出 "a", "b"(不包含"c")

​​

特点​​:

​​无异常抛出​​:适合多线程场景,允许并发修改。

​​数据一致性弱​​:可能读取到过时数据(如 CopyOnWriteArrayList 的迭代器基于旧副本)。

2.HashMap​​​​底层实现​​:

(1)​​JDK 1.8前​​:数组+链表(链表过长时查询效率退化到O(n))。

​​JDK 1.8+​​:数组+链表/红黑树(链表长度≥8时转红黑树,查询效率优化到O(logn))。

​​(2)​​核心机制​​:哈希计算​​:(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)(高位异或减少哈希冲突)。

​​扩容​​:默认容量16,负载因子0.75,扩容时容量翻倍(resize()触发链表/树节点拆分)。

​​ 线程不安全场景​​:多线程同时触发扩容可能导致链表成环(JDK 1.7)或数据丢失。

​​(3)​​对比ConcurrentHashMap​​:

分段锁(JDK 1.7)​​:锁粒度粗,性能较差。

​​CAS+synchronized(JDK 1.8)​​:锁单个数组元素(头节点),支持更高并发。

(3)​​AOP(面向切面编程)的原理详解​​

​​1. 核心思想​​

AOP 通过​​动态代理​​技术,将与业务无关的​​横切关注点​​(如日志、事务、权限)从业务代码中分离,实现​​解耦​​和​​代码复用​​。

AOP的增强通常是通过切面+切点+通知来完成的, 在创建bean的时候发现bean和切点表达式匹配就会创建动态代理。而事务内置一个增强类, 在创建bean的时候, 一旦发现你的类加了@Transactional注解 就会创建动态代理。在执行AOP的bean时会先执行动态代理的增强类, 通过责任链分别按顺序执行通知。在执行事务的bean的时候会先执行动态代理的增强类, 在执行目标方法前进行异常捕捉,出现异常回滚事务, 无异常提交事务。

AOP 的底层基于两种代理模式:

​​ JDK 动态代理​​

​​适用场景​​:目标类实现了接口。

​​实现流程​​:

通过 Proxy.newProxyInstance() 生成代理对象。

代理类实现 InvocationHandler 接口,在 invoke() 方法中织入增强逻辑。

public class JdkProxy implements InvocationHandler {

private Object target; // 目标对象

public Object bind(Object target) {

this.target = target;

return Proxy.newProxyInstance(

target.getClass().getClassLoader(),

target.getClass().getInterfaces(),

this

);

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 前置增强(如开启事务)

System.out.println("Before method: " + method.getName());

Object result = method.invoke(target, args); // 执行目标方法

// 后置增强(如提交事务)

System.out.println("After method: " + method.getName());

return result;

}

}

(2)CGLIB 动态代理​​

​​适用场景​​:目标类未实现接口。CGLIB使用ASM(一个Java字节码操作框架)来生成字节码。因为要继承某个类使用了final无法进行继承

​​实现流程​​:

通过 Enhancer 生成目标类的子类代理。

实现 MethodInterceptor 接口,在 intercept() 方法中织入增强逻辑。

public class CglibProxy implements MethodInterceptor {

public Object getProxy(Class<?> clazz) {

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(clazz); // 设置父类

enhancer.setCallback(this); // 设置回调

return enhancer.create(); // 生成代理对象

}

@Override

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

// 前置增强

System.out.println("Before method: " + method.getName());

Object result = proxy.invokeSuper(obj, args); // 调用父类方法

// 后置增强

System.out.println("After method: " + method.getName");

return result;

}

}

4.JDK动态代理和CGLIB动态代理的区别

(1)从性能上特性对比:

JDK动态代理要求目标对象必须实现至少一个接口,因为它基于接口生成代理类。而CGLIB动态代理不依赖于目标对象是否实现接口,可以代理没有实现接口的类,它通过继承或者代理目标对象的父类来实现代理。

(2)从创建代理时的性能对比:

JDK动态代理通常比CGLIB动态代理创建速度更快,因为它不需要生成字节码文件。而CGLIB动态代理的创建速度通常比较慢,因为它需要生成字节码文件。另外,JDK代理生成的代理类较小,占用较少的内存,而CGLIB生成的代理类通常较大,占用更多的内存。

(3)从调用时的性能对比:

JDK动态代理在方法调用时需要通过反射机制来调用目标方法,因此性能略低于CGLIB(调用父类),尽管JDK动态代理在Java 8中有了性能改进,但CGLIB动态代理仍然具有更高的方法调用性能。CGLIB动态代理在方法调用时不需要通过反射,直接调用目标方法,通常具有更高的方法调用性能,同时无需类型转换。

5.Spring是如何解决Bean的循环依赖?

Spring是如何解决的循环依赖: 采用三级缓存解决的 就是三个Map ; 关键: 一定要有一个缓存保存它的早期对象作为死循环的出口

(1)一级缓存singletonObjects存完整单例bean。

(2)二级缓存earlySingletonObjects存放的是早期的bean,即半成品,此时还无法使用(只用于循环依赖提供的临时bean对象)。

(3)三级缓存singletonFactories (循环依赖的出口,解决了循环依赖)。它存的是一个对象工厂,用于创建对象并放入二级缓存中。同时,如果对象有Aop代理,则对象工厂返回代理对象

6. 索引,什么场景适合/不适合创建索引

(1)适合创建索引的场景

频繁的查询操作:如果某个列经常用于查询条件(如 WHERE 子句),则创建索引可以提高查询速度。

大数据量表:在大表中,索引可以显著减少数据扫描的时间。

排序和分组:如果经常需要对某个列进行排序(ORDER BY)或分组(GROUP BY),索引可以加速这些操作。

多表连接用的字段适合建立索引

(2)不适合创建索引的场景

频繁的写操作:如果表中数据频繁更新、插入或删除,维护索引的开销可能会影响性能。

小数据量表:对于小表,索引的维护成本可能超过其带来的查询性能提升。

区分度不高的列:如果某个列的不同值很少(例如性别列),则创建索引的效果有限。

where检索用不到的字段

7. 索引区分度

是指索引中某一列的唯一值数量与该列总记录数的比率。它反映了索引的有效性和查询性能。高区分度意味着索引能够更有效地缩小搜索范围,从而提高查询效率。

区分度可以通过以下公式计算:区分度= 总记录数 / 唯一值的数量

高区分度:当区分度接近1时,表示该列的值非常独特,索引效率高。适合用于查找、排序和连接操作。

全部评论

相关推荐

高斯林的信徒:武大简历挂?我勒个骚岗
点赞 评论 收藏
分享
球Offer上岸👑:可能是大环境太差了 太卷了 学历也很重要 hc也不是很多 所以很难
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务