学习笔记——HashMap总结
优点:查询速度快,理想状态下时间复杂度是O(1)
缺点:空间利用率不高
HashMap是通过数组加链表来实现的。所以一个好的hash算法需要能将key值尽可能地均匀分布到整个数组中。但是再好地hash算法也无法避免
随着数组位置被填充得越来越多,计算后落在数组同一位置的概率会越来越大。所以HashMap通过加载因子控制整个数组的使用率,默认加载因子是0.75,
当整个数组的使用率达到了0.75,就会进行扩容。也就是说,HashMap中至少四分之一的数组空间是被浪费了的。这也就是为什么HashMap的空间利用率不高。
另外HashMap的扩容会带来不小的性能开销,因为需要将原来的key根据扩容后新的数组大小进行rehash。像ArrayList这种可变长数组一样,
如果能预估到所要存放的数据量比较大的话,最好在初始化的时候就直接指定大小。这样可以避免频繁的扩容,导致不必要的性能开销。
这里需要注意下,HashMap初始化的时候,并不是直接初始化成你指定的大小。HashMap中的源码如下:
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); }
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; }v
实际初始化的大小,是不小于你指定大小的最小的2的n次幂。如指定17,那最终初始化的大小就是2的5次幂32。
再来说下链表,当有多个key被分配到数组的同一个位置上时,就形成了链表。链表的时间复杂度是比较差的O(n),所以要尽可能地避免产生链表。
在JDK1.8中,HashMap引入了红黑树对链表进行了优化。当链表长度达到8的时候,会自动将链表转换成红黑树(时间复杂度是O(logN))。
当树的节点数减少到6的时候,再自动变换成链表。