总是觉得对HashMap很熟悉,但最近连续被问到几个关于它的问题,才发现它其实并不简单。这里对关于它的一些问题做个总结,也希望能够大家一个参考。

都知道它是基于hash值,可以进行常量时间消化的存储结构。广泛用于各种情况下的高效key-value存储。这里有几个问题。首先,如果出现hash值冲突该怎么处理?相信很多人都不用思考就能说出,通过链表来解决冲突。就是将hash值相同值存储到一个挂载在该位置的链表里面去。但是这就又引出了新的问题,给一个key,它的hash值有冲突的情况下,它是如何在链表上取到你所期望的value的?这个就要去看hashmap的存储结构了。每一个key-value会被封装到一个entity中,map中存储的其实是这个entity。这样既存储了value,又存储了key。所有在链表上取值只需要比较key是否equal。这里又出现了一个问题,为什么建议重写key的equal和hash方法。重写hash方法能够保证不同对象用于不同的hash值,从而减少冲突,重写equal则可以在出现冲突的情况下,保证不出现错误覆盖的情况。看一下hashmap的put方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)//没有初始化,则要初始化,初始化调用的也是resize()方法
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);//这个位置没有值,直接插入,重写hash方法的作用体现在这里
else {//出现了冲突
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))//找到了如该key equal的value
e = p;
else if (p instanceof TreeNode)//事树节点,插入到树里。jdk8冲突节点数目达到一定值后,使用树结构存储
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {//一直找到链表的结尾,将k-v插入
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);//是否需要改成树存储
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

上面的代码充分表明了key重写equal的作用。HashMap以key的value来获取值,所以一定要确保同一个key的hashcode在任何时候都不能改变。这也是为什么建议key是不可变对象,如string对象。如果key是对象,运行过程中key的hashcode因为内部值的改变而发生了改变,那么map中的value将永远丢失。

以上是关于kv的讨论,接下来是关于HashMap的另外的一个常见话题——线程安全问题。多数人都知道它是非线程安全的。但是如果问你它非线程安全体现在哪里,恐怕会难住一批人。首先容易想到的是多线程插入时出现了冲突的情况,多个线程同时插入,但是这其中有些值又出现了冲突。插入时大家都看到这个位置没有值,于是都进行插入,这样肯定会出现值覆盖,对外的表现就是值丢失。如果开始插入时,这个位置已经有了值,那么在插入链表过程中还是会出现值覆盖。另外就是同时扩容问题。因为HashMap会在空间不足时自动扩容,大小变成之前的两倍。同时复制之前的值到新的数组中。冲突链也会进行复制。如果多个线程插入后同时看到容量需要调整,就都会调用resize方法。那么底层到达会出现什么问题就难以预测了。所以这也是HashMap非线程安全的第二点体现。当然同时读写一个值也可能会存在数据跟期望不一致的情况。这也是非线程安全的表现。

以上就是HashMap的一些相关问题。个人体会还是需要注重细节,自己看源码,才会有更深入的体会。

最新文章

  1. 配置linux----------------ip
  2. 小技巧,关于OC打印指针地址和arc下的retaincount
  3. Filter
  4. 关于NGINX的502的装逼打怪之路
  5. MONGODB 与sql聚合操作对应图
  6. Spring中加载配置文件的方式
  7. Appium移动自动化测试(二)--安装Android开发环境
  8. PV UV
  9. GRUB加密
  10. Python Tutorial 学习(六)--Modules
  11. BIGINT UNSIGNED value is out of range in … 问题的解决方法
  12. 一个模拟时钟的时间选择器 ClockPicker
  13. check单选框多个全选与取消全选
  14. Spring4 MVC 多文件上传(图片并展示)
  15. Web基础知识和技术
  16. Java多线程和死锁
  17. 一个解释volatile关键字最好的例子
  18. Git与SVN交叉使用
  19. oracle控制文件问题
  20. asp.net MVC 5 路由 Routing

热门文章

  1. Nginx搭建图片服务器
  2. PostgreSQL Replication之第四章 设置异步复制(3)
  3. 新人 记录VUE中分页实现
  4. Java基础学习总结(21)——数组
  5. Java 学习(12):重写(Override)与重载(Overload) &amp; 多态
  6. 洛谷 P2839 畅通工程
  7. 《JSP+Servlet+Tomcat应用开发从零開始学》
  8. 书剑恩仇录online全套源代码(服务端+client+文档)
  9. p2p項目夭折,有種蛋蛋的憂傷。。
  10. USACO2011 Jan:公司利润