
Java HashMap
!
警告:这篇文章创作时长大于 730 天,其内容可能已经过时。
简介
HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
结构
JDK1.8以上的HashMap采用的是数组+链表+红黑树结构实现的。
- HashMap类内部维护了一个哈希桶数组,它是一个Node的数组,采用静态内部类Node来实现
|
|
- HashMap就是使用哈希表来存储的。为了解决哈希冲突,Java中的HashMap采用链地址法来解决,即哈希桶数组的每个元素上都是链表结构。
- 当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。
实现
HashMap的内部功能实现有很多,主要分析获取索引、 put方法和扩容机制三个方面的实现。
获取索引
获取索引主要采用以下方法来实现。
- 计算
key
的hashCode
值:h = key.hashCode()
- 高位运算:
h ^ (h >>> 16)
- 取模运算:
h & (length - 1)
其中
length
代表哈希桶数组的长度
put方法
- HashMap的put方法执行过程可以通过下图来理解
- put方法过程
- 判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
- 根据键值key计算hash值得到插入的数组索引i,如果table[i]为null,直接新建节点添加,转向6,如果table[i]不为空,转向3;
- 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向4,这里的相同指的是hashCode以及equals;
- 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向5;
- 遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
- 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
- 附JDK1.8HashMap的put方法源码
|
|
扩容机制
当HashMap中包含的Entry
的数量大于等于threshold = loadFactor * capacity
的时候,且新建的Entry
刚好落在一个非空的桶上,此刻触发扩容机制,将其容量扩大为2倍。
其中
threshold
代表元素的数量阈值,laodFactor
代表HashMap的负载因子,默认为0.75
,capacity
代表哈希桶数组的长度。
由于Java中的数组无法自动扩容,所以扩容的方法是使用一个新的数组代替已有的容量小的数组。 以下是JDK1.8中的扩容源码
|
|
线程安全性
在多线程使用场景中,应该尽量避免使用线程不安全的HashMap,而使用线程安全的ConcurrentHashMap,在并发的多线程使用场景中使用HashMap可能造成死循环。