文章目录
-
-
- Q1. 默认初始化大小为什么是 16 而不是 8 或者 32 ? 为什么不直接写 16 ,而是写 1<<<4 ?
- Q2. 默认加载因子为什么是 0.75 ?
- Q3. 为什么有最小树形化容量阈值 64?
- Q4. HashMap 的 table 的容量如何确定?loadFactor 是什么?该容量如何变化?这种变化会带来什么问题?
- Q5. 为什么会扩容 ? 什么时候会扩容 ? 怎么扩容 ?
- Q6. 默认初始化大小为什么定义为2的幂 ?
- Q7. 为何 hashCode 值进行右移运算/异或运算 ?
- Q8. HashMap 中 put 方法的过程 ?
- Q9. HashMap 和 HashTable 有什么区别 ?
- Q10. Java 中的另一个线程安全的与 HashMap 极其类似的类是什么?同样是线程安全,它与 HashTable 在线程同步上有什么不同?
- Q11. HashMap & ConcurrentHashMap 的区别 ?
-
Q1. 默认初始化大小为什么是 16 而不是 8 或者 32 ? 为什么不直接写 16 ,而是写 1<<<4 ?
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
如果太小,4或者8,扩容比较频繁;如果太大,32或者64甚至太大,又占用内存空间
位运算更快,不需十进制和二进制相互转换
Q2. 默认加载因子为什么是 0.75 ?
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
加载因子表示哈希表的填满程度,跟扩容息息相关。为什么不是0.5或者1呢?
如果是0.5,就是说哈希表填到一半就开始扩容了,这样会导致扩容频繁,并且空间利用率比较低。 如果是1,就是说哈希表完全填满才开始扩容,这样虽然空间利用提高了,但是哈希冲突机会却大了。
作为一般规则,默认负载因子(0.75)在时间和空间成本上提供了良好的权衡。负载因子数值越大,空间开销越低,但是会提高查找成本(体现在大多数的HashMap类的操作,包括get和put)。设置初始大小时,应该考虑预计的entry数在map及其负载系数,并且尽量减少rehash操作的次数。如果初始容量大于最大条目数除以负载因子,rehash操作将不会发生。
简言之, 负载因子0.75就是冲突的机会 与空间利用率权衡的最后体现,也是一个程序员实验的经验值。
Q3. 为什么有最小树形化容量阈值 64?
/**
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
static final int MIN_TREEIFY_CAPACITY = 64;
如果数组长度小于 64, 这个时候树形化,治标不治本,因为引起链表过长的根本原因是数组过短。
所以在JDK1.8源码中,执行树形化之前,会先检查数组长度,如果长度小于64,则对数组进行扩容,而不是进行树形化。