内容简介:1.构造函数后的hashmap都是空的,size=0,只有在第一次put的时候,才会调resize扩容2.index = (n - 1) & hash 实际是做了一个取模, 因为n是2的n次方, 所以n-1二进制都是13.if index节点为空,则new Node
#### put
1.构造函数后的hashmap都是空的,size=0,只有在第一次put的时候,才会调resize扩容
2.index = (n - 1) & hash 实际是做了一个取模, 因为n是2的n次方, 所以n-1二进制都是1
3.if index节点为空,则new Node
else 哈希冲突
如果节点哈希值相等,key也相等,则是覆盖value操作
如果节点是树, 红黑树先不深入,反正就是做了查找插入或替换
如果是链表小于8,就遍历,插入或替换,注插入后如果链表长度大于8了,还要做treeifyBin转换为红黑树操作
4.然后++modCount, if (++size > threshold) resize();
5.扰动函数
hashcode散列值分布再松散,要是只取最后几位的话,碰撞也会很严重.
右位移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。
Peter Lawley的一篇专栏文章《An introduction to optimising a hashing strategy》里的的一个实验,实验证明:在没有扰动函数的情况下,发生了103次碰撞,接近30%。而在使用了扰动函数之后只有92次碰撞。碰撞减少了将近10%。看来扰动函数确实还是有功效的。
源码:
public V put(K key, V value) { //先根据key,取得hash值。 再调用上一节的方法插入节点 return putVal(hash(key), key, value, false, true); } static final 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) { //tab存放 当前的哈希桶, p用作临时链表节点 Node<K,V>[] tab; Node<K,V> p; int n, i; //如果当前哈希表是空的,代表是初始化 if ((tab = table) == null || (n = tab.length) == 0) //那么直接去扩容哈希表,并且将扩容后的哈希桶长度赋值给n n = (tab = resize()).length; //如果当前index的节点是空的,表示没有发生哈希碰撞。 直接构建一个新节点Node,挂载在index处即可。 //这里再啰嗦一下,index 是利用 哈希值 & 哈希桶的长度-1,替代模运算 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else {//否则 发生了哈希冲突。 //e Node<K,V> e; K k; //如果哈希值相等,key也相等,则是覆盖value操作 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p;//将当前节点引用赋值给e else if (p instanceof TreeNode)//红黑树暂且不谈 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//不是覆盖操作,则插入一个普通链表节点 //遍历链表 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) {//遍历到尾部,追加新节点到尾部 p.next = newNode(hash, key, value, null); //如果追加节点后,链表数量》=8,则转化为红黑树 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //如果找到了要覆盖的节点 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //如果e不是null,说明有需要覆盖的节点, if (e != null) { // existing mapping for key //则覆盖节点值,并返回原oldValue V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; //这是一个空实现的函数,用作LinkedHashMap重写使用。 afterNodeAccess(e); return oldValue; } } //如果执行到了这里,说明插入了一个新的节点,所以会修改modCount,以及返回null。 //修改modCount ++modCount; //更新size,并判断是否需要扩容。 if (++size > threshold) resize(); //这是一个空实现的函数,用作LinkedHashMap重写使用。 afterNodeInsertion(evict); return null; }
putAll
public void putAll(Map<? extends K, ? extends V> m) { //将另一个Map的所有元素加入表中,参数evict初始化时为false,其他情况为true putMapEntries(m, true); } //将另一个Map的所有元素加入表中,参数evict初始化时为false,其他情况为true final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { //拿到m的元素数量 int s = m.size(); //如果数量大于0 if (s > 0) { //如果当前表是空的 if (table == null) { // pre-size //根据m的元素数量和当前表的加载因子,计算出阈值 float ft = ((float)s / loadFactor) + 1.0F; //修正阈值的边界 不能超过MAXIMUM_CAPACITY int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); //如果新的阈值大于当前阈值 if (t > threshold) //返回一个 》=新的阈值的 满足2的n次方的阈值 threshold = tableSizeFor(t); } //如果当前元素表不是空的,但是 m的元素数量大于阈值,说明一定要扩容。 else if (s > threshold) resize(); //遍历 m 依次将元素加入当前表中。 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } }
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- ReactNative源码解析-初识源码
- Spring源码系列:BeanDefinition源码解析
- Spring源码分析:AOP源码解析(下篇)
- Spring源码分析:AOP源码解析(上篇)
- 注册中心 Eureka 源码解析 —— EndPoint 与 解析器
- 新一代Json解析库Moshi源码解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。