为什么要了解hashmap内部结构,其实有几个目的。1是面试用,这些小东西面试官很喜欢,虽然大部分公司不会正真需要你去了解,但是市场就是这样。2是如果有幸进入大数据行业,tb pb zb级别数据处理的时候可能会用到这些的知识。 说明:以下例子都以JDK8为例。
心理模型
hashmap是key-value结构,底层实现基础是一个数组结构,通过把key进行hash映射,变成数组索引号,从而实现快速查找目的。数组结构的优点是读取很快,但是插入和删除效率太低,所以就产生了纵向的链表和树型结构。当hash映射的索引一样的时候,就在这个索引位置形成链表或者树型结构,具体是链表还是树型取决于链表数据量大小,当链表数据量数据超过8个的时候,形成红黑树结构。
static final int TREEIFY_THRESHOLD = 8;
大小限制
HashMap是有大小限制的,这点从size()返回int数据类型可以看出,至少不能超过Integer.MAX_VALUE,实际是2的30次方。当超过最大数量后,HashMap不再进行扩容,理论上还可以往里面存取数据,只是size会出错,但是最先出现的应该是OutOfMemory。
static final int MAXIMUM_CAPACITY = 1 << 30;
扩容机制
在HashMap元素数量达到一个阈值3/4的时候,会进行自动扩容把容量扩大2倍。 知道这个原理也有个好处,如果可以预估出数量大小,初始化map的时候指定初始化大小,这样省去了扩容时间,提高程序性能。
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;
}
代码看不懂,不要慌张,写个小小测试用例:
for (int i = 16; i <= 100; i++) {
System.out.println(i + " : " + tableSizeFor(i));
}
输出:
16 : 16
17 : 32
18 : 32
...
32 : 32
33 : 64
34 : 64
...
64 : 64
65 : 128
66 : 128
...
可以看出,如果i代表容量大小,tableSizeFor代表是的大于容量最近2^n数值。
hash映射算法
hash & (length - 1)
length代表以上映射数组长度,hash代表key默认hash值,一般我们认为直接把hash % length就可以求出对于数组索引了。这里有个小技巧,当length为2^n时候,位运算& (length -1)的效果就等效于%运算,hash结果均匀分布情况也是一样的。 代码看不懂,不要慌张,写个小小测试用例:
int length = 16;
for (int i = 16; i <= 100; i++) {
System.out.println(i + " -> " + (i % length) + " " + (i & (length - 1)));
}
输出:
16 -> 0 0
17 -> 1 1
18 -> 2 2
...
结果完全一样。
线程安全
HashMap不是线程安全的,这点从源代码一个synchronized关键词也没有可以看出来。所以适用于一下场景: 1,单线程里面; 2,多线程但是只读。