聊聊HashMap

HashMap是基于哈希算法的键值对存储容器,底层使用散列桶实现,当发生碰撞时使用链表存储,java8引入了红黑树来提升性能。HashMap中有很多设计需要我们取考虑,比如通过hash()计算index;在put()和get()时,equal()和hashCode()都有什么作用;如何综合考虑空间利用率和查找效率,等等。本文就从这些方面来聊聊HashMap。

1.HashMap基本原理


table即为散列桶,当put操作时,通过哈希算法计算索引(index),决定元素添加到哪个散列桶下。当不同的元素key的hashCode相同时,就发生了哈希碰撞。这时HashMap通过链表来解决哈希碰撞问题。为了综合考虑空间利用率和查找效率,HashMap定义了负载因子(loadFactor)和容量(capacity)。当size > (loadFactor * capacity),就会扩容2倍,然后重新哈希(resize)。

2.在put()和get()时,equal()和hashCode()都有什么作用

put

  • 1.对key的hashCode()做hash,然后计算index [index = (n-1)&hash]
  • 2.如果没有哈希碰撞就直接放到bucket[index]里(bucket[index]中没有元素)
  • 3.如果碰撞了,以链表的形式存入bucket[index] (java8中可能使用treeMap)
  • 4.如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换为红黑树 聊聊红黑树
  • 5.如果节点已经存在,就替换旧值(key以存在,保证key的唯一性)
  • 6.如果bucket满了(超过load factor*current capacity),就要resize。
    key.hashCode()是用来计算index的,决定元素存入哪个散列桶;key.equals()用来判断散列桶中是否已经存在该键值(保证key的唯一性)

get

  • 1.对key的hashCode()做hash,然后计算index [index = (n-1)&hash]
  • 2.如果桶中没有元素,返回null
  • 3.如果桶中有元素,通过key.equals()查找链表/treeMap

3.hash()实现

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

p = tab[i = (n - 1) & hash]


可以看到这个函数大概的作用就是:高16bit不变,低16bit和高16bit做了一个异或。
设计者的解释是(综合考虑了速度、作用、质量),就是把高16bit和低16bit异或了一下。设计者还解释到因为现在大多数的hashCode的分布已经很不错了,就算是发生了碰撞也用O(logn)的tree去做了。仅仅异或一下,既减少了系统的开销,也不会造成的因为高位没有参与下标的计算(table长度比较小时),从而引起的碰撞。
对此有人通过信息论角度解释到,这是为了利用高位的信息。

4.HashMap如何扩容

为了综合考虑空间利用率和查找效率,HashMap定义了负载因子(loadFactor)和容量(capacity)。当size > (loadFactor * capacity),就会扩容2倍,然后重新哈希(resize)。
HashMap如何resize的问题上其实设计也很巧妙:当resize时,因为我们使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。
怎么理解呢?例如我们从16扩展为32时,具体的变化如下:

假设bucket大小n=2^k,元素在重新计算hash之后,因为n变为2倍,那么新的位置就是(2^(k+1)-1)&hash。而2^(k+1)-1=2^k+2^k-1,相当于2^k-1的mask范围在高位多1bit(红色)(再次提醒,原来的长度n也是2的次幂),这1bit非1即0。如图:

所以,我们在resize的时候,不需要重新定位,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话位置没变,是1的话位置变成“原位置+oldCap”。

5.至此,我们对HashMap的讨论基本结束了,接下来对hashmap的其他内容做简要介绍:

1.HashMap可以接受null键值和null值(HashTable不能);HashMap是线程不安全的,多线程环境下,推荐使用ConcurrentHashMap。
2.String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。

参考资料:
Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例
Java HashMap工作原理及实现
HashMap的工作原理

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值