HashMap的数据结构
- 数组+链表+红黑树
- 数据是以节点的方式来存储的,每个节点中包含:Key,Value,Next指针,
链表是为了规避哈希冲突而存在的,因为哈希冲突理论上是不能避免的。
红黑树是为了解决链表长度存在多的时候,解决效率问题而存在的, 节点大于8,
默认大小是16,负载因子是0.75
面试题
所有的问题都是对知识点的理解,如果有不懂的问题,需要反向反思自己是否理解这个知识点。
- put同一个key的时候,返回值是之前的值。(源码中是这样写的。)
- HashMap的数据结构是怎么样的?为什么会使用链表结构,为什么会使用数组结构,为什么1.8开始会使用红黑树
a. 数组1.7存的就是Key-Value的Entry,1.8寸的时候Node - 链表新增数据的时候,是放在头结点还是尾结点。?
a. 1.7采用的链表的头插法 (多线程扩容的时候会导致循环卡死。)
b. 1.8采用链表的尾插法 - get的逻辑和问题,get的时候先经过hash计算只能取到数组的位置。(先取到头结点)
- 为什么HashMap在初始化数组容量的时候,一定要是2的次方数呢?
- 如何解决Hash冲突导致的链表很长的问题?会导致get方法很慢!
- HashMap的数组为什么需要扩容?==》为了提高get的效率。
- HashMap扩容的步骤是什么呢?
- HashMap扩容会导致哪些问题呢? 链表翻转
- HashMap不是线程安全的,多线程扩容可能会让链表循环搬运。搬运的时候,对导致两个节点形成循环链表。
a. 多线程扩容的时候,会导致这个问题
b. 问题原因:链表插入使用的是头插法,头插法的特性。两个线程共同调用扩容的时候就会出现循环。所以1.8版本就把它改成了尾插法。 - HashMap什么时候会进行rehash()
- HashMap的key为null的时候,是存在index为0的位置上的。
相关代码摘要
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
这个方法太牛了。获取最近位置的2的次方数。
只能感叹JDK作者对于位运算的使用已经达到了出神入化的境界了。
问题:为什么HashMap在初始化数组容量的时候,一定要是2的次方数呢?
newTab[e.hash&(newCap-1)]=e;
需要进行或操作,来计算数组下标位置方法的时候,这个算法的应用就倒着推出来的newCap必须要是2的次方。
问题:如何解决Hash冲突导致的链表很长的问题?会导致get方法很慢!
H ^= k.hashcode()
这个方法为了提高最终hashcode的散列性
参考链接: