您的位置:首页 > 其它

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

2017-10-27 15:27 483 查看

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(...));</preHashMap类所有集合视图返回的迭代器都是“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); }}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: