hashMap--put(k,v)源码分析

来源:互联网 发布:mac srt 字幕乱码 编辑:程序博客网 时间:2024/06/05 14:32

1. hashMap中的注释:

Hash table是基于Map接口的实现。这个版本的实现提供了所有map操作的实现并且允许null值和null键
除了允许空值(null)和不支持同步,HashMap和hashtable没有什么区别
HashMap不保证有序,尤其是不保证顺序随时间不变
该实现提供常数时间的get、put操作,假设hash函数使元素均匀分布在bucket中。

一个HashMap实例有两个参数会影响性能:初始容量(initial capacity)和负载因子(load factor)
capacity是hashtable的桶数目,初始容量仅仅只是创建时的容量
负载因子是衡量哈希表自动增长前装满的程度
当哈希表中键值对的数目超出容量与负载因子之积,哈希表就会重新哈希(rehashed)(即,内部数据结构会重建)为之前容量的两倍

通常,默认的负载因子0.75可以在时间和空间之间有很好的权衡,想避免重新hash的时间开销,可以设置比较大的初始容量
太多关键字的hashCode()相同也会导致性能下降,HashMap实现使用关键字之间的比较顺序来平衡

要特别注意HashMap实现不支持同步。如果有多个线程同步访问一个HashMap,并且一个以上的线程会修改HashMap结构,则需要外部同步;(结构修改是指添加或移除键值对,仅仅修改value值不是结构修改操作)
如果没有这样的对象,map需要由Collections.synchronizedMap()方法包装 ,而且最好在Map创建时完成,以防意外的非同步访问
非同步访问map:<pre>
Map m = Collections.synchronizedMap(new HashMap(...));</pre
HashMap类所有集合视图返回的迭代器都是“fail-fast”:迭代器创建之后修改map结构,除了使用迭代器自己的remove方法,其他都会抛出ConcurrentModificationException异常
但这种行为也不能保证同步不出错


2.来看一个put()方法的完整实现

public static void main(String[] args) {
//
Map map=new HashMap();
map.put("1","1");
}

new HashMap();HashMap的构造函数: 默认了0.75 的负载因子

public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}


put()方法:key和value一起包装成一个Node

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

hash函数:是一个int类型的返回

static final int hash(Object key) {
int h;
//null 键的hash值为0
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//(h = key.hashCode()) ^ (h >>> 16):再次异或运算,避免hash冲突的设计

hashCode()函数:

public int hashCode() {
int h = hash;//默认是0
if (h == 0 && value.length > 0) {
char val[] = value;
//按字符拆分,累计hash值
for (int i = 0; i < value.length; i++) {
h = 31h + val[i];
}
hash = h;
}
return h;
}

31h + val[i]公式可以理解为:

s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
如字符串:"yuan".hashCode();的计算步骤:
// 第一步 = (int)'y'
// 第二步 = (31(int)'y') + (int)'u'
// 第三步 = 31((31(int)'y') + (int)'u') + (int)'a'
// 第四步 = 31(31((31(int)'y') + (int)'u') + (int)'a') + (int)'n'

使用31的原因如下:

1.31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)
2.31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)
3.选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
4.并且31只占用5bits,相乘造成数据溢出的概率较小。


putVal方法的源码分析:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab是各链表的头节点(或者是红黑树根节点)组成的数组;
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果当前tab对象为null或者它内部没有任何元素,那么resize()分配内存空间,resize()完成后将元素数量赋值给n,并且将对象的Node数组赋值给tab了
if ((tab = table) == null || (n = tab.length) == 0)
//这里分配重新分配空间后的大小
n = (tab = resize()).length;
//大小n-1 表示下标地址和hash值求与,判断数组中是否已经存在, 如果没有就new一个Node,放在p节点上,同时也是链表的头节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {//链表表头节点存在话
//判断是否需要覆盖当前节点,或者创建新的节点
Node<K,V> e; K k;
//判断该位置上的Node的hash值和key,是否和传入的参数一致
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//hash值和key值一致,那么直接将这个元素赋值给e;也就是覆盖原有节点的数据
e = p;
//不一致,判断这个Node是不是属于TreeNode(红黑树)上的节点,是的话,插入到红黑树中;所以是整个链表都转化为红黑树
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//如果不是,那么就是说这个位置已经有别的元素了,进行循环对比内部的单向链表;后面的元素继续插入会变成链表的形式加在后面,
else {
for (int binCount = 0; ; ++binCount) {//binCount表示链表的长度
//查询到链表的最后一个节点没有指向的引用,新建一个Node,并将引用存入p.next
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 如果链表的长度大于8,那么就需要转化成红黑树
//static final int TREEIFY_THRESHOLD = 8 //下标是7
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//量大于8转化整个链表为红黑树
treeifyBin(tab, hash);
break;
}
//后面的链表中存在一个和传入参数一致的Node,那么获取并赋值
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//找到这个key对应的Node了,那么对这个Node的value进行覆盖,然后返回它原来的value
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;
}


newNode(hash, key, value, null);:创建新的节点,传入null,表示该节点后没有下一个节点

Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}


Node<K,V> 是hashMap的一个内部类: 单向链表的设计

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
//下一个节点的引用,也可以看出来是单向链表的概念
Node<K,V> next;

//构造函数
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}

public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }

public final int hashCode() {
//key和value同时做hashCode()计算,取异或集合
return Objects.hashCode(key) ^ Objects.hashCode(value);
}

public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}

public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}

//整个单链表转化为红黑树,并确定根节点是头结点

final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//如果当前哈希表为空,或者哈希表中元素的个数小于 进行树形化的阈值(默认为 64),就去新建/扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) { //大于了64的数组长度
//红黑树的头(head)、尾节点(tail)
TreeNode<K,V> hd = null, tl = null;
//转化节点类型和构建前后链接
do {
//将当前的节点转换为红黑树中树节点
TreeNode<K,V> p = replacementTreeNode(e, null);
//尾部节点为空,放在该节点上
if (tl == null)
hd = p;
else {
//不为空,该节点前指针,之前前面的节点
p.prev = tl;
//前面的节点next指向,指向本节点
tl.next = p;
}
//
tl = p;
} while ((e = e.next) != null);
//一个元素是红黑树的头结点,将从该结点链接到的结点组成红黑树
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}


//将从该结点链接到的结点组成红黑树

final void treeify(Node<K,V>[] tab) {
//默认根节点为null
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
//左右子树默认是null
x.left = x.right = null;
if (root == null) {
x.parent = null;//默认该节点没有父亲节点
x.red = false;//节点设置为黑色
root = x;//该节点为根节点
}
else {
//key
K k = x.key;
//hash值
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);

TreeNode<K,V> xp = p;
//判断放在哪个子树上,如果子树为空,再处理
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
//数据插入后平衡一下红黑树的结构
root = balanceInsertion(root, x);
break;
}
}
}
}
//确保给定根节点是当前容器中的第一个结点
moveRootToFront(tab, root);
}

//确定根节点为首地址节点

static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;
if (root != null && tab != null && (n = tab.length) > 0) {
//索引位置
int index = (n - 1) & root.hash;
//通过索引位置获取首节点
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
//判断root不是首节点,则转化root节点为首节点
if (root != first) {
Node<K,V> rn;
//赋给首节点
tab[index] = root;
//处理引用指向
TreeNode<K,V> rp = root.prev;
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
if (first != null)
first.prev = root;
root.next = first;
//根节点的前部分设为空
root.prev = null;
}
assert checkInvariants(root);
}
}