HashMap 几大问题

一、HashMap的底层数据结构

JDK1.7:数据+链表 

jdk1.8:数组+链表+红黑树 ;如果链表存储超过了8个,那么会调用treeifyBin函数,将链表转换为红黑树。那么即使hashcode完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(log n)的开销 ; 也就是说put/get的操作的时间复杂度最差只有O(log n)。

二、HashMap的存取原理

存储:

  • 我们调用put(key,value)方法的时候,其实我们会先对键key调用key.hashcode()方法,根据方法返回的hashcode来找到bucket的位置来存Node对象。(Node对象存有key和value)
  • 如果hashcode相同,那么它们对应的bucket显然也是相同的,这个时候就会产生所谓的碰撞(hashmap的底层存储结构是 数组+链表+红黑树)。每个bucket索引对应一个链表,这个时候系统就会找到对应的链表,并在链表的尾部加上这个Node对象( jdk1.7 采用的是头插法,jdk1.8才是尾插法)。
  • 如果hashcode相同,那么它们对应的bucket显然也是相同的,这个时候就会产生所谓的碰撞(hashmap的底层存储结构是 数组+链表+红黑树),这个时候会使用 equals() 方法比较key 的值是不是相同,如果相同则覆盖

获取:

  • 当我们调用get(key)的时候,会调用key的hashcode方法获得hashcode.
  • 根据hashcode获取相应的bucket。
  • 由于一个bucket对应的链表中可能存有多个Entry,这个时候会调用key的equals方法来找到对应的Entry
  • 最后把值返回

三、默认初始化大小是多少

默认是16,这是一个经验值,如果太小,这样会频繁的扩容很消耗性能;如果太大会浪费内存

采用2的 n 次幂可将取余运算转化未位运算,因为位运算直接对内存数据进行操作,不需要转成十进制,所以位运算要比取模运算的效率更高。

static int indexFor(int h, int length) {
    return h & (length-1);
}

四、HashMap的负载因子是多少

HashMap的扩容方式?负载因子是多少?为什是这么多?

HashMap的扩容机制:HashMap的扩容条件就是当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容;在HashMap中,threshold = loadFactor * capacity。

loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f,设置成0.75有一个好处,那就是0.75正好是3/4,而capacity又是2的幂。所以,两个数的乘积都是整数;对于一个默认的HashMap来说,默认情况下,当其size大于12(16*0.75)时就会触发扩容。

指定容量初始化

  • 当我们通过HashMap(int initialCapacity)设置初始容量的时候,HashMap并不一定会直接采用我们传入的数值,而是经过计算,得到一个新值,目的是提高hash的效率。(1->1、3->4、7->8、9->16)
  • 在JDK 1.7和JDK 1.8中,HashMap初始化这个容量的时机不同。JDK 1.8中,在调用HashMap的构造函数定义HashMap的时候,就会进行容量的设定。而在JDK 1.7中,要等到第一次put操作时才进行这一操作。

五、hash的计算规则

static final int hash(Object key) {
    int h;
    return (key == null) ?
    0 : 
    (h = key.hashCode()) ^ (h >>> 16);
}
  • 如果 key 为 null,hash == 0
  • 否则
    • 先取出 key 的 hashCode 值,得到 h1
    • 然后对 h1 右移 16 位,得到 h2
    • 最后将 h1 和 h2 进行异或运算,得到最终的 hash 值

 

古之学者为己,今之学者为人
原文地址:https://www.cnblogs.com/jalja365/p/15590738.html