Java面试-HashMap原理
一、二叉树相关:
- 二叉树定义:每个节点有两个叉,包含左右子节点,不要求每个节点都有两个子节点,其左右子树也符合该特征。
- 实现方式:可用数组或链表实现,用链表实现时,类TreeNode有value、left、right三个属性。
- 分类:有满二叉树、完全二叉树、二叉搜索树、红黑树等,本次主要讲二叉搜索树和红黑树。
- 二叉搜索树:
- 特点:又名BST树等,任意节点左边子树的值小于该节点,右边子树的值大于该节点,且左右子树也符合此特点。
- 作用:支持快速查找、动态插入和删除数据。
- 时间复杂度:平均情况下为O(log n),极端情况退化为链表时为O(n)。
- 红黑树:
- 性质:节点要么红要么黑,根节点必为黑,叶子节点为黑色空节点,红色节点子节点为黑色,从任意节点到叶子节点所有路径包含相同数目黑色节点。
- 作用:保证树的平衡,使时间复杂度稳定。
- 时间复杂度:查找、添加、删除均为O(log n)。
二、散列表:
- 基本概念:又名哈希表,根据键直接访问内存存储的值,由数组演化而来,利用数组按下标随机访问特性,时间复杂度为O(1)。
- 散列函数:将键映射为数组下标的函数,要求计算的哈希值为大于等于0的正整数,相同键哈希值相同,不同键哈希值不同,但此条较难实现。
- 散列冲突:多个键映射到同一数组下标位置,解决办法是拉链法,即数组下标对应链表,链表过长可改为红黑树,可提升效率、避免DOS攻击。
- 操作时间复杂度:插入为O(1),平均查找和删除为O(1),大量冲突时链表查找和删除退化为O(n),改为红黑树后查找为O(log n)。
三、哈希map实现原理:
- 底层结构:数组+链表+红黑树。
- 数据存储:put元素时用key的哈希code重新计算哈希值定位数组下标,可能产生哈希冲突,key相同则覆盖value,不同则存入链表或红黑树(链表长度大于8且数组长度大于64时转红黑树)。
- 数据获取:通过key的哈希值找到数组下标获取元素。
- 版本区别:JDK 1.7采用数组+链表,JDK 1.8采用数组+链表+红黑树,链表长度大于8且数组长度大于等于64时转红黑树,扩容时红黑树节点小于等于6个退化为链表。
四、哈希map put方法流程:
- 属性:默认容量为16,加载因子为0.75,阈值为容量乘以加载因子,table是存储数据的NODE数组,size是集合元素个数。
- 流程:第一次添加先判断table是否为空,为空则调用resize初始化长度为16的数组,根据key计算索引,若索引处为空则添加数据,添加后判断size是否大于阈值,大于则扩容;非第一次添加,根据key计算索引,若索引处不为空,判断key是否存在,存在则覆盖value,判断是否为红黑树,是则走红黑树添加逻辑,否则遍历链表,key存在则覆盖,不存在则在尾部添加节点,链表长度大于等于8则转红黑树。
五、哈希map扩容机制:
- 触发条件:添加元素或初始化时调用resize方法,第一次添加初始化长度为16的数组,数组元素超过阈值(数组长度乘以0.75)时扩容。
- 扩容方式:容量变为原来的两倍,创建新数组,将旧数组数据挪到新数组,有三种情况,无冲突节点取哈希值模新数组长度定位下标,红黑树走红黑树添加逻辑,链表遍历拆分,节点哈希值按位与老容量等于0则留在原下标,不等于0则存到原下标加老容量的位置。
六、哈希map寻址算法:
- 哈希值计算:调用key的hashCode方法得到哈希值,右移16位后与原哈希值做异或运算,使哈希值更均匀,减少冲突。
- 索引计算:用数组长度减1按位与哈希值代替取模,性能更好,数组长度必须是2的n次幂,这样计算索引和扩容时重新计算索引效率更高。
七、哈希map 1.7多线程死循环问题:
- 底层结构:数组+链表,扩容时链表迁移采用头插法。
- 死循环原因:多线程下,一个线程先完成扩容使链表顺序颠倒,另一个线程迁移时可能导致节点相互指向,产生死循环。
- 解决办法:JDK 1.8采用尾插法避免死循环。