HashMap的底层原理:
1. 数据存储结构: HashMap在JDK1.8及之后版本中,底层使用数组+链表+红黑树的存储结构。
2. put操作过程: 判断数组是否为空:若为空,则进行首次扩容。 计算索引:根据key的hashCode值与数组长度1进行位运算得到索引。 直接插入:若该索引位置为null,则直接插入键值对。 覆盖value:若该位置不为null且key相同,则覆盖value。 链表或红黑树插入: 若key不同且为红黑树节点,则在红黑树中插入。 若为链表节点,则遍历链表插入,若key已存在则覆盖value。 链表长度>=8且数组元素数量>=64时,链表转为红黑树。 链表长度>=8但数组元素数量<64时,先扩容。 扩容判断:插入后判断数组元素数量是否超过阈值,若超过则进行扩容。
3. get操作过程: 计算索引:根据key的hashCode值找到在数组中的位置。 返回null:若该位置为null,则返回null。 返回value:若key相同,则返回对应的value。 红黑树或链表查找:若为红黑树节点,则按红黑树查找;若为链表节点,则按链表查找。
4. 扩容机制: 初始容量为16,每次扩容容量为原来的2倍。 扩容时,先创建新数组,再将旧数组中的数据根据规则转移到新数组中。
5. 初始容量为16的原因: 减少hash碰撞,提高存储效率。 在效率和内存使用上权衡,避免频繁扩容和浪费资源。
6. 扩容以2的整数次幂进行的原因: 使得的二进制全为1,位运算时能充分散列,避免哈希冲突。
7. 链表转红黑树的原因: 提高查找效率,当链表长度过长时,转为红黑树以优化性能。
8. 线程不安全性: JDK1.7中,并发扩容可能导致死循环和数据丢失。 JDK1.8中,并发put操作可能导致数据覆盖。
9. 解决哈希冲突的方法: 使用拉链法,当链表长度过长时转为红黑树。
10. 使用红黑树而非其他树的原因: 红黑树在插入和删除上性能优于平衡二叉树,且查询性能略低但可接受。 避免二叉查找树和B/B+树的缺点,如线性增长和节点过多导致的遍历效率降低。