JAVA面试题(1)
说说 Java 中 HashMap 的原理?
为什么引入红黑树: 当hash冲突较多的时候,链表中的元素会增多,插入、删除、查询的效率会变低,退化成O(n)使用红黑树可以优化插入、删除、查询的效率,logN级别。
转换时机: 链表上的元素个数大于等于8 且 数组长度大于等于64; 链表上的元素个数小于等于6的时候,红黑树退化成链表。
链表插入方式变更:从"头插法"改为"尾插法"
-
头插法特点:
- 插入时不需要遍历链表
- 直接替换头结点
- 扩容时会导致链表逆序
- 多线程环境下可能产生死循环
-
尾插法改进:
- 避免扩容时的链表逆序
- 解决多线程环境下的潜在死循环问题
Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?
ConcurrentHashMap 不同JDK版本的实现对比
- 数据结构
-
JDK1.7:
- 使用
Segment(分段锁) + HashEntry数组 + 链表
的数据结构
- 使用
-
JDK1.8及之后:
- 使用
数组 + 链表/红黑树
的数据结构(与HashMap类似)
- 使用
- 锁的类型与宽度
-
JDK1.7:
- 分段锁(Segment)继承了
ReentrantLock
- Segment容量默认16,不会扩容 → 默认支持16个线程并发访问
- 分段锁(Segment)继承了
-
JDK1.8:
- 使用
synchronized + CAS
保证线程安全 - 空节点:通过CAS添加(put操作,多个线程可能同时想要将一个新的键值对插入到同一个桶中,这时它们会使用 CAS 来比较当前桶中的元素(或节点)是否已经被修改过。)
- 非空节点:通过synchronized加锁,只锁住该桶,其他桶可以并行访问。
- 使用
- 渐进式扩容(JDK1.8+)
- 触发条件:元素数量 ≥
数组容量 × 负载因子(默认0.75)
- 扩容过程:
- 创建2倍大小的新数组
- 线程操作数据时,逐步迁移旧数组数据到新数组
- 使用
transferIndex
标记迁移进度 - 直到旧数组数据完全迁移完成
500. 什么是 Java 的 CAS(Compare-And-Swap)操作?
CAS操作包含三个基本操作数:
- 内存位置(V):要更新的变量
- 预期原值(A):认为变量当前应该具有的值
- 新值(B):想要更新为的值
CAS 工作原理:
- 读取内存位置V的当前值为A
- 计算新值B
- 当且仅当V的值等于A时,将V的值设置为B
- 如果不等于A,则操作失败(通常重试)
伪代码表示:
if (V == A) {
V = B;
return true;
} else {
return false;
}