【十八掌●基本功篇】第一掌:Java之HashMap

这一篇博文是【大数据技术●降龙十八掌】系列文章的其中一篇,点击查看目录:这里写图片描述大数据技术●降龙十八掌


HashMap是经常使用的一个类型,它有一些特点:
(1) 键值允许为null。
(2) 是非同步、线程不安全的类
(3) 不能保证按照插入顺序排序,也不能保证不同的时间,顺序不变。
(4) 按照key读写速度比较快。

HashMap类图

HashMap类图

(1) HashMap:HashMap是根据key的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但是遍历顺序但是不确定的。HashMap最多只允许一条记录的键为null,HashMap也是非线程安全的,如果在多线程场景下使用,可以用Collections的synchronizedMap方法保证线程安全。

(2) LinkedHashMap:LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在遍历时先插入的值肯定是先被读取到的。

(3) TreeMap:TreeMap是根据键的顺序排好序的,默认是根据键升序排序,也可以指定降序排序。因为要根据键进行排序,所以键必须要实现Comparable接口或者在构造TreeMap时候传入自定义的Comparator。

数据结构

HashMap实际存储时,是哈希桶数组和链表的结合体,整体上来说是一个数组,每个数据元素又是一个链表,当链表长度大于8时,这个链表就变为红黑树形式。

这里写图片描述

存储结构如上图所示,HashMap初始化的时候就会创建一个哈希桶数组,哈希桶数组的长度是capacity(容量)定义的大小,数组的每一项是一个Entry,Entry 是一个键值对,它还有一个指向下一个元素的引用,就构成了链表,如果链表长度大于8时,Java8开始,为了提高效率,链表转换为红黑树形式。(红黑树参考

当向HashMap中插入数据时,先根据数据key的hashCode计算index确定放入哪个哈希桶数组中,如果多个key的hashCode发生碰撞,就以链表的形式放入数组的后面,当链表大于8时,转换为红黑树。

哈希桶数组的容量capacity默认是16,当哈希桶数组中数据个数已经占用的比例已经达到负载因子时候(默认是0.75),哈希桶数组的容量就会变为原来的2倍。负载因子是对空间和时间的一个权衡。

put操作

put函数大致的思路为:

  • 对key的hashCode()做hash,然后再计算index;
  • 如果没碰撞直接放到bucket里;
  • 如果碰撞了,以链表的形式存在buckets后;
  • 如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树;
  • 如果节点已经存在就替换old value(保证key的唯一性)
  • 如果bucket满了(超过load factor*current capacity),就要resize。

get操作

  • 根据Key的hashCode值,在bucket里找到第一个节点,直接命中;
  • 如果有冲突,则通过key.equals(k)去查找对应的entry。
    若为树,则在树中通过key.equals(k)查找,时间复杂度为O(logn);
    若为链表,则在链表中通过key.equals(k)查找,时间复杂度为O(n)。

put和get归纳起来就是:

简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 hash 算法来决定其在数组中的存储位置,在根据 equals 方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry 时,也会根据 hash 算法找到其在数组中的存储位置,再根据 equals 方法从该位置上的链表中取出该Entry。

扩容机制

当 HashMap 中的元素越来越多的时候,hash 冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对 HashMap 的数组进行扩容。当 HashMap 中的元素个数超过数组大小乘以负载因子时,就会进行数组扩容,负载因子的默认值为 0.75,这是一个折中的取值。也就是说,默认情况下,哈希桶数组大小为 16,那么当 HashMap 中元素个数超过 16乘以0.75=12 的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,扩容时,要将原来的数据复制到一个新创建的大数组中,然后重新计算每个元素在新数组中的位置。而这是一个非常消耗性能的操作,所以如果我们已经预知 HashMap 中元素的个数,那么预设元素的个数能够有效的提高 HashMap 的性能。

线程安全性

在多线程使用场景中,应该尽量避免使用线程不安全的HashMap,而使用线程安全的ConcurrentHashMap。

参考资料:
https://tech.meituan.com/java-hashmap.html
http://wiki.jikexueyuan.com/project/java-collection/hashmap.html
https://yikun.github.io/2015/04/01/Java-HashMap%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE%9E%E7%8E%B0/

发布了74 篇原创文章 · 获赞 74 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/chybin500/article/details/78953636