IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> HashMap 添加源码分析 -> 正文阅读

[数据结构与算法]HashMap 添加源码分析

HashMap

源码解析

添加源码解析
resize()

这个方法在putval中执行,因为要确保你数组不是null

resize就是重新计算容量;向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素;当然java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组;就像我们用一个小桶装水,如果想装更多的水,就得换大水桶。

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

int oldThr = threshold;

第一次的时候threshold,初始化是0

int oldCap = (oldTab == null) ? 0 : oldTab.length;

oldTab是那个原来的数组,第一次没东西所以长度是0,进else语句

newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

这行说的是第一次进来我们的容量是16,负载因子是0.75,按照这个结果也就是12,当数组长度超过12时进行扩容

Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];

第一次初始化,newCap = DEFAULT_INITIAL_CAPACITY;,也就是16,所以初始化数组长度为16

if (oldTab != null) {

oldTab是原来的table,原来table默认初始化是null

至此直接return newTab;

总结:

第一次进来,首先会看你这个数组的长度,没有就是0。所以接下来他就去初始化临界值为12,根据默认长度初始化数组为16个容量,因为是第一次oldTable是null,所以直接返回初始化的结果,此时table初始化成功,长度为16(Node<K,V>[16])

putVal()
首次添加
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        
    
    ~~~~~
    
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

if ((p = tab[i = (n - 1) & hash]) == null)

初始化完成后,他会去根据数组长度-1与计算的hash码进行与运算的出来放的数组位置,他会去看这个位置有没有元素

有呢就创建一个Node放到这个位置就可以了

如果不是null那就只能链表操作了,那个操作等会说

if (++size > threshold)
       resize();
afterNodeInsertion(evict);
return null;

之后呢,我们要变更size的大小,因为size是记录有多少个真正的元素在这个数组中,threshold就是临界值,我们初始化好了,这个值就是12,这个if呢就是为了判断,你添加到数组的元素,你不能大于这个临界值,那你要是大于了,那我就去扩容去了,否则了那就没事了

afterNodeInsertion(evict);

这玩意是为了LinkedHashMap服务的,是为了保证顺序而存在的,在hashmap中没意义。

下次添加

本次添加不会再进第一个if,因为第一个if是判断table是否为null进行初始化的,所以他会先走这句

if ((p = tab[i = (n - 1) & hash]) == null)
           tab[i] = newNode(hash, key, value, null);

还是判断计算出位置看有没有值,没值的话直接插入就行

当出现相同的值时,他所的出来的位置是一样的,所以上面的判断不成立,直接走到else语句。

也就是说出现了可能出现hash冲突

else {
           Node<K,V> e; K k;
           if (p.hash == hash &&
               ((k = p.key) == key || (key != null && key.equals(k))))
               e = p;
           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);
                       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;
               }
           }
           if (e != null) { // existing mapping for key
               V oldValue = e.value;
               if (!onlyIfAbsent || oldValue == null)
                   e.value = value;
               afterNodeAccess(e);
               return oldValue;
           }
       }

在这里面有三种情况

第一种 判断全都是否一致

if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))

因为

if ((p = tab[i = (n - 1) & hash]) == null)

这句话虽然判断不成立,但也会给p这个变量赋值,这个p就代表了这次相同位置的元素的值Node

首先p.hash == hash 这句,是当前位置的hash值是否与你现在的哈希值是否一样(此处的哈希值是无符号右移16位在异或的值)

之后((k = p.key) == key || (key != null && key.equals(k)))这个是一个整体先看第一部分

(k = p.key) == key ,他会去看p节点的key值是否与你传入的key是不是同一个对象

(key != null && key.equals(k)),会判断你传入的是不是一个null 并且不是的话调用equals()方法判断是不是相等

那么就把当前位置的这个值p拿出来给e

第二种

p instanceof TreeNode

看当前节点是不是一个TreeNode对象类型

是的话

e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

走这个,强转赋值

第三种,啥也不是那就是真正的出现了hash冲突

for (int binCount = 0; ; ++binCount) {
   if ((e = p.next) == null) {
       p.next = newNode(hash, key, value, null);
       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;
}

依次判断当前这个节点的next节点有没有东西,没有的话那就直接指向这个新的节点也就是你传入的

if (binCount >= TREEIFY_THRESHOLD - 1)这句话很关键,也就是说他会一直循环看看你这个链表的长度是多少,如果你比8大,也就是转成红黑树的因子还要大的时候,treeifyBin(tab, hash);给你穿成了红黑树,break掉

第二个if呢,是看当前链表节点的next节点的hash值是否如你的传入的hash是一样的,并且next节点的key值如你的key是否是一个对象或者在你这key不是空的条件下,调用equals方法判断是否一致。说白了就是tm的你传入的节点的值,引用和当前节点完全一样就是同一个东西那就直接break了,什么也不干;(注意此时比较的是next与你传入的是否一致)

if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
    afterNodeAccess(e);
    return oldValue;
}

就是一个值的覆盖

再说一下树化,发生在第三种for循环里的添加,那时候会判断是否满足先决的树化条件

final void treeifyBin(Node<K,V>[] tab, int hash) {
       int n, index; Node<K,V> e;
       if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
           resize();
       else if ((e = tab[index = (n - 1) & hash]) != null) {
           TreeNode<K,V> hd = null, tl = null;
           do {
               TreeNode<K,V> p = replacementTreeNode(e, null);
               if (tl == null)
                   hd = p;
               else {
                   p.prev = tl;
                   tl.next = p;
               }
               tl = p;
           } while ((e = e.next) != null);
           if ((tab[index] = hd) != null)
               hd.treeify(tab);
       }
   }

首先会把table和hash传入,判断table也就是数组是不是空的,或者数组的长度是不是小于64(MIN_TREEIFY_CAPACITY)如果满足了就进行扩容而不是树化

也就是说树化必须是:存在链表长度大于8,数组长度必须大于64的时候才会发生

之后呢,(e = tab[index = (n - 1) & hash]) != null这行也就是说你添加的key要插入在数组中的位置必须不等于空才能树化,这不是废话吗。

do while((e = e.next) != nul)不断循环当前这个数组的节点位置上的链表,把每一个转换成TreeNode(replacementTreeNode(e, null)),进行添加树节点prev是上边的next下边的

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-01 14:44:55  更:2021-08-01 14:46:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 18:46:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码