一、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 值