1. hashmap是由数组加链表组成的,数组是hashmap的哈希桶,链表是为解决哈希碰撞而存在的,如果定位到的数组位置不含链表(即哈希桶中只有一个entry),则对于查找、添加等操作很快在,只有O(1),只需要一次寻址即可(数组根据下标寻址),如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增。对于查找操作来讲,也需要遍历链表,然后通过key对象的equals方法逐一比较查找。所以,就性能考虑,hashmap中的链表越少,则性能越好。即,哈希表的hash值越离散,entry就会尽可能均匀的分布,出现链表的概率就越低。
2. hashMap不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历。
3. hashMap有两个有参构造器可以用来设置initialCapacity和loadFactor的值,即HashMap的初始容量和负载因子的值,如果不传则使用默认值。
4. 在jdk7中,hashMap处理碰撞问题时,都是采用链表来存储,当碰撞的节点很多时,查询的时间是O(n)。
在jdk8中,hashMap处理碰撞问题增加了红黑树这种数据结构,当碰撞节点较少(<8)时,采用链表存储,当碰撞节点较大(>8)时,则采用红黑树(特点是查询时间为O(nlogn))存储。 工作:前面产生冲突的那些key对应的记录只是简单的追加到链表的头部,这些记录只能用遍历来查找,但是超过了阈值后,hashmap开始将列表升级为一个二叉树,使用hash值作为树的分支变量。如果两个hash值不等,但指向同一个桶的话,较大的那个会插到右子树中。若hash值相等,hashMap希望key值最好是实现了comparable接口,这样就可以按照顺序来进行插入。如果没有实现这个接口,则性能提升就很难实现。
为什么一开始不使用红黑树?→因为红黑树维护比较困难。
5. 红黑树的扩容:hashMap中的数组(哈希桶)默认初始长度为16,但是当数组中有数据的个数超过总数的0.75时,hashMap就会自动扩容。
6. 如果对象会用hashMap/hashSet/hashTable等构成对象的集合时,如果重写equals方法必定要重写hashcode方法。
原因:equals和hashcode都是java的基类Object类的方法。equals方法是用于比较两个值是否相等,hashcode主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括hashset、hashMap、hashTable。 如果补充些hashcode方法,则对相同内容的两个对象的hash值不同,会导定位到hashMap的不同哈希桶中,因此不会get到值。
7.hashMap的并发问题:
hashMap不能在并发场景下使用,因为在hashMap的源码中,它的所有方法都没有同步处理,实际上只要是在堆中的对象,如果在多线程情况下使用而不做同步处理的话,都有可能导致数据不一致的问题。
hashMap的哈希桶中的数据正常情况下是单向链表,而jdk1.7中的hashMap在并发情况下有可能形成环形链表。环形链表的产生是在扩容阶段产生的。
在jdk1.8中修复了产生环形链表的问题,即使用两个局部链表进行扩容不会出现哈希环。但是jdk1.8中的hashMap仍然不能在并发场景下使用,因为hashMap的put方法在并发下会出现丢数据的问题。
对于hashMap的并发问题,有以下解决方案:
①使用hashTable,hashTable是线程安全的。但HashTable源码好像只是在方法上加上了synchronize方法,而synchronize 属于重量级锁,对效率损耗是比较大的。
② 使用jdk1.8中的ConcurrentHashMap,这个类实现了更高级的线程安全,且使用分段锁,在并发下也可有很高的吞吐量,推荐使用。、
参考链接:
https://blog.csdn.net/tuke_tuke/article/details/51588156
https://lushunjian.github.io/blog/2019/01/02/HashMap%E7%9A%84%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0/