HashMap
HashMap可以看成是一个 `数组+链表/红黑树` 组合成的数据结构。
HashMap,HashTable,ConcurrentHashMap是基于hash表的实现,HashTable和HashMap在代码实现上,基本上是一样的,一个是线程安全的,一个非线程安全。ConcurrentHashMap也是线程安全的,但性能比HashTable好。
重要点
在hashMap中放入(put)元素,有以下重要步骤:
- 计算key的hash值,获得元素在底层数组中的下标。
- 通过下标位置定位到底层数组里的元素。
- 取到第一个元素,判断放入元素的key和当前位置的key是否==或者equals,成立则替换value值,返回旧值。
- 如果是树,循环树中的节点,判断放入元素的key和当前节点的key是否==或者equals,成立则替换树里的value,并返回旧值,不成立就添加到树里。
- 第一个元素不匹配就顺着元素的链表结构循环节点,判断放入元素的key是否==或equals节点的key,成立则替换链表里value,并返回旧值,找不到就添加到链表的最后。
判断放入HashMap中的元素要不要替换当前节点的元素,key满足以下两个条件即可替换:
- 1、hash值相等。
- 2、==或equals的结果为true。
由于hash算法依赖于对象本身的hashCode方法,所以对于HashMap里的元素来说,hashCode方法和equals方法非常重要,所以equals方法和hashCode方法一般是绑定重写的, 否则会造成hashMap运行结果不正确!
几个常量
static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子
transient int size; //逻辑长度
transient int modCount; //修改次数, 用于fail-fast
int threshold; //阈值,用于扩容,一般等于 (loadFactor * capacity)
//没有空数组初始化会造成空指针异常
static final Node<?,?>[] EMPTY_TABLE = {};
//HashMap的主干数组,就是一个Node数组,初始值为空数组{},主干数组的长度一定是2的次幂。
transient Node<K,V>[] table = (Node<K,V>[]) EMPTY_TABLE;
static final int TREEIFY_THRESHOLD = 8; //链表红黑树转换阈值
数组元素
数组里原对象是Node。
static class Node<K,V> implements Map.Entry<K,V>{
final int hash; //hash值
final K key;
V value;
Node<K,V> next; //指向下一个Node的引用,组成链表结构
Node(int hash, K key, V value, Node<K,V> next) {
//...省略构造函数
}
}
添加Key-Value对
public V put(K key, V value){
return putVal(hash(key), key, value, false, true);
}
计算hash值
static fianl int hash(Object key){
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //高低位优化,降低碰撞几率
}
添加到链表
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n,i;
/**添加元素时table为空,触发resize()**/
if((tab = table)==null || (n = table.length) == 0)
n = (tab.resize()).length;
/** 添加元素时 @if tab[i]位置上没有元素,直接添加
* n为table的逻辑长度,即size
* i = (n - 1) & hash 算法可以保证 i 在 n-1 和 hash 之间
* @else 遍历 通过equals和==判断链表上key和hash是否和目标相同,
* 是则替换,否则添加至链表尾部
**/
//1。从底层数组取值
if((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); //数组元素转成一个单向链表
else{
Node<K,V> e; K k;
//2。底层数组元素匹配成功,赋值给e
if((hash == p.hash) &&
((k = p.key) == key || (key != null && key.equals(k)))
e = p;
//3。树结构处理
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//4。底层数组第一个没匹配上,遍历查询匹配
else {
for (int binCount = 0; ; ++binCount) {
//添加元素到尾部
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) //链表元素大于阈值
treeifyBin(tab, hash); //转成红黑树
break;
}
//同2。
if (e.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))
break;
p = e;
}
}
//替换value
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
添加到树
//more code here
扩容
hashMap的扩容由resize方法实现, 这个resize方法只是简单实现, 不是源码.
private Node<K, V>[] resize() {
int newCap = 0;
//数组为空,初始化一个数组
if (table.length == 0 || table == null){
newCap = DEFAULT_INITIAL_CAPACITY;
Node<K,V>[] newTab = (Node<K,V>[]) new Node[ newCap ];
table = newTab;
return newTab;
}
//数组逻辑长度超过阈值,扩容并复制原数组,数组长度要保证是2的幂次数.
else if (size >= (threshold = (int) (loadFactor * table.length))){
newCap = table.length * 2;
Node<K,V>[] newTab = (Node<K,V>[]) new Node[ newCap ];
transfer(newTab);
table = newTab;
return newTab;
}
//超过最大长度
else if (size > MAXIMUN_CAPACITY){
newCap = HashMap2.MAXIMUN_CAPACITY;
return table;
}
return null;
}
/**
* 把原数组的元素传到扩容后的数组
*/
void transfer(Node[] newTable) {
int newCapacity = newTable.length;
for (Node<K,V> e : table) {
while(null != e) {
Node<K,V> next = e.next;
int i = e.hash & (newCapacity - 1);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}