원칙 및 소스 코드 분석을 해시 맵

원칙 및 소스 코드 분석을 해시 맵
HashMap- 프로필 디자인

영상

도 쉬운 이해를 위해 일정 기간을 확인합니다.

  • 표 어레이라는 통 (통)
  • 이러한 테이블과 같은 테이블 어레이 요소 [. 1] 슬롯이라고
  • 슬롯은 슬롯이라고리스트에 대응하는 하나의 링크 된리스트 데이터 (슬롯 포함한 데이터)가 있으면

도 슬롯에 해당하는리스트 테이블이 노드 수 8 같다보다 클뿐만 아니라, 데이터의 양이 더이 경우 테이블 (64)은, 상기 레드 - 블랙 트리를 통해 변환되지 않는다 초과 결정하더라도, 실제로 위에있는 부정확성을 이상은하지 않고 레드 - 블랙 트리로 변환되는 테이블의 확장이 될 것입니다

상술 한 바와 같이, 조성물 HashMap의 다음 구성 요소로 구성되는

  • 테이블 배열
  • 단일 목록
  • 레드 - 블랙 트리 (후속 설명에만 특정 상황에서 존재)
생각

전에 소스 코드를 볼 수 있지만, 그 전에 우리는 소스 코드를 볼 수있는 문제에 대한 깊은 이해와 함께, 숙고하는 몇 가지 질문이

  1. HashMap의 확장은 putVal 해시 저장 방법 다시 그것을 할를 호출하는 각 데이터에 뒤집어 아닌가요?
  2. 왜 하나의 목록 번호 8 이상은 이상 또는 64 테이블 시간의 배열과 동일는 레드 - 블랙 트리로 변환 할?
  3. 왜 HashMap에 달성하기 위해 배열 + + 목록 레드 - 블랙 트리를 사용하려면?
  4. 확장 오히려 크기 = 시간의 table.length보다 확장 할 때 왜 크기> 임계 한계 값이 존재 하는가?
  5. 왜, 언제 레드 - 블랙 트리 노드의 수는 6 개 미만의 목록으로 감소한다?
  6. HashMap의 경우, 때 구현 될 때 어떻게 확장의 효율성을 개선하기 위해 다시 해시를 필요로하지 않는 확장?
  7. HashMap에 그것을 주문한다?
  8. HashMap의 스레드는 안전합니까?

질문 2 : 배열은 레드 - 블랙 트리로 변환 할 때 64보다 크거나 같은 이유는 단일 연결 목록과 테이블 번호 8?

여기에 우리가 먼저 언급해야 이진 검색 트리를 , 우리는 링크 된 목록으로 변질, 극단적 인 경우 이진 검색 트리를 알고

영상

이것은 그의 모습 매우 효율적인 O (log2n), 하나는 균형 이진 검색 트리입니다 그림에서 볼 수있는 반면에 그림 II 우리가 비슷한 유지할 수 있습니다, 그래서 만약 단일 연결리스트로 분해 효율 O (N)를 찾기 이 상태의 도면, 우리는 효율성을 유지하기 위해 볼 수 있으므로 이진 검색 트리의 균형을 붉은 검은 나무는 가장 널리 산업에 사용되는 균형 이진 검색 트리로 연결됩니다. 나무가 균형 잡힌 상태로 유지되도록 노드를 추가하여 레드 블랙 트리, 빨간색 노드와 하위 트리 및 기타 작업을 회전 블랙 색상을 수정 레드 - 블랙 트리 원리는 복잡하지 않지만 코드를 검색 갈 수있는 상대적으로 복잡 관심있는 학생을 달성하는 것입니다 여기에 일시적으로 말을하지 마십시오 레드 - 블랙 트리를 설명하기 위해 별도의 장소에서 설명했다.

여기에 우리가 이유가 레드 - 블랙 트리로 변환해야합니다 알고 변환 할 때 테이블 자체가 확장 사실, 작은 경우, 왜 이하 8 이상의 슬롯과 테이블은이 수익금의 균형 때문, 64 이상의 배열을 나열 가격이 매우 낮거나 매우 높은 효율이다.

질문 3 : HashMap의 달성 배열 + + 목록 레드 - 블랙 트리를 사용하는 이유는 무엇입니까?

배열 인덱스의 위치를 ​​저장하기 위해 테이블을 사용하여 인덱스 위치 (키) 및 해시 기반으로 - 직접 배열 인덱스 (table.length 1) 계산 테이블을 계산 한 다음 키의 다음 방문은 직접 (1을 O의 효율성을 액세스 할 수 있습니다 우리는 그것의 목록을 증가해야하는 이유 데이터가 다른 슬롯에 해시 할 경우에도 해시 알고리즘에 대한 문제 해시 알고리즘, 해시 알고리즘 자체는 높은 효율, 작은 해시 충돌이 너무 좋은이기 때문에),이입니다 해시를 결정하기 위해 하나 하나를 검색 할 때 해시 알고리즘은, 사실, 완전히, 해시 충돌은 확실히 존재하는 등의 충돌이있는 경우, 해시, 그것은 충돌의 끝에 데이터를 추가하기 위해 슬롯 데이터를 머리에있을 것입니다, 보장 할 수 없습니다 키가 발견 해당 데이터와 같은지 여부를 확인하고, 그래서 검정 트리 질문 2 설명 하였지만

질문 4 : 크기> 임계 한계 값이 왜이 확장은 확장을 수행 할 때?

때문에 어떤 상황 용량의 가능성이 높을수록, 데이터의 양이 저장된 상기 큰 그때는 시간 확장 임계 액을 수득 해시 충돌 새롭게 데이터를 추가 필요가되는 것은 확장 용 HashMap에 필요한 값까지의 저장 용량을 나타낸다

질문 5 : 왜 링크 된 목록으로 변질하는 레드 - 블랙 트리 노드 6보다 작은 횟수는?

단일 연결리스트 노드의 데이터가 충분히 작은 경우, 탐색 시간은 충분히 빨리 무시할 수, 검색 속도입니다. 목록의 복잡성도 HashMap의 확장의 낮은 유지 보수와 레드 - 블랙 트리보다 더 나은,, 또한 목록을 분할하는 것이 더있을 수 있습니다

다른 문제에 관해서는 우리는 소스에서 답을 찾을뿐만 아니라 코드는 위의 작업을 달성하는 방법을 볼 필요가있다. 첫째, (특정 코드를 설명 할 때 일부는 일반적인 인상을 여기에, 그것은 중요하지 않습니다 이해하지 못하는) 표현 정의의 멤버 변수 중 일부의 의미를 살펴

변수 정의
    // 默认的初始容量为 16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    // 最大容量为 2^30
    static final int MAXIMUM_CAPACITY = 1 << 30;

    // 默认的装载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    // 当一个桶中链表元素个数大于等于 8 的时候转化为红黑树
    static final int TREEIFY_THRESHOLD = 8;

    // 当一个桶中链表元素个数小于等于 6 的时候将红黑树转化为链表
    static final int UNTREEIFY_THRESHOLD = 6;

    // 当桶的个数达到 64 的时候并且单个槽位链表结点数量大于等 8 的时候进行树化
    static final int MIN_TREEIFY_CAPACITY = 64;

    // 数组,也就是桶
    transient Node<K,V>[] table;

    // 作为 entrySet() 的缓存
    transient Set<Map.Entry<K,V>> entrySet;

    // 元素的数量
    transient int size;

    // 修改次数,用于在迭代的时候执行 fail-fast
    transient int modCount;

    // 当桶的使用数量达到多少时候进行扩容
    int threshold;

    // 装载因子
    final float loadFactor;

    /**
     * 单链表结点
     * @param <K>
     * @param <V>
     */
    static class Node<K,V> implements Map.Entry<K,V> {
        // 存储 key 的 hash 值
        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() {
            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;
        }
    }
复制代码
생성자
    // 指定容量和装载因子构建 HashMap
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                    initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                    loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    // 指定容量构造 map
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    // 无参构造全部使用默认值
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    // 根据 map 来创建一个 HashMap 使用默认的参数
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        // 将传入 map 数据复制到新的 map 中
        putMapEntries(m, false);
    }
    
复制代码

위의 방법은 변수는, 파라미터 : initialCapacity loadFactor 용량과 부하 계수, 모든 기본 값을 다른 들어오는 양 맵을 기반으로 새의 HashMap을 구축 할 수있는 마지막 생성자는 이제 살펴 보자 때 지침을 지정할 수 있습니다지도를 작성하는 것보다 더 아무것도 아니다 putMapEntries (m, 거짓) 방법을 실현한다.

    // 方法是 final 不可被覆写
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            // 如果桶数组还没有创建,则先进行创建
            if (table == null) { // pre-size
                // 使用传入 map 的长度 / 装载因子 + 1 作为当前的 map 容量
                float ft = ((float)s / loadFactor) + 1.0F;
                // 如果该值超出 map 承受的最大值,则取 map 最大值作为容量
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                        (int)ft : MAXIMUM_CAPACITY);
                // 如果 map 当前的容量超出了扩容限定值,则进行扩容
                if (t > threshold)
                    // 扩容并且返回新的 map 扩容限定值
                    threshold = tableSizeFor(t);
            }
            // 如果 map 的数据大小超出了扩容阀值
            else if (s > threshold)
                // 将数据迁移到一个新的 map 中搬移所有数据
                resize();
            // 遍历传入 map
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                // 将数据一个一个的重新放入 map 中
                putVal(hash(key), key, value, false, evict);
            }
        }
    }
复制代码

여기) (tableSizeFor (t)는 여기 tableSizeFor을 보면, 임계 값을 증가 해당 섹션에 넣어 putVal ()의 새로운 HashMap의 데이터로 바뀝니다) puVal를 (사용하기 위해 호출

    /**
     * 返回一个大于等于且最接近 cap 的 2 的幂次方整数
     * cap 无符号右移 1 位然后位或, 然后右移 2 位然后位或 3 .. 得到最终的结果
     * @param cap
     * @return
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
复制代码
데이터를 삽입
    public V put(K key, V value) {
        // onlyIfAbsent:false 表示如果存在则更新,不存在则插入
        return putVal(hash(key), key, value, false, true);
    }
    
    /**
     * 根据传入 key 的 hashCode 的无符号右移 16 位次方作为其 map 中的 hash 值
     * @param key
     * @return
     */
    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) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 如果 table 为 null 先进行初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 如果 hash 后制定的槽位为 null 则直接放入数据即可
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            // 槽位存在数据就需要检查槽位链表是否存在对应的数据
            // 如果有根据策略选择是更新还是放弃
            // 如果没有这执行插入
            Node<K,V> e; K k;
            // 已经存在对应的 key 直接进行赋值后续根据 putIfAbsent 决定是否更新
            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 {
                // binCount 临时统计链表数量
                for (int binCount = 0; ; ++binCount) {
                    // 如果不存在对应的 key 则直接执行插入
                    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;
                    }
                    // 如果已经存在对应的结点则直接返回后续根据 onlyIfAbsent 决定是否更新
                    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;
            }
        }
        // 修改次数 + 1
        // 该字段用于后续迭代器 fail-fast 
        ++modCount;
        // 数据量大于 threshold 进行 table 扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

复制代码

몇 가지 주요 포인트가 위의 코드는, 요약

  • 슬롯은 데이터를 직접 삽입하지 않으면
  • 데이터가 검출되는 경우, 삽입 슬롯 노드 키는 현재의 데이터 슬롯을 업데이트 할 필요가있는 현재 슬롯 아니다
  • 키를 업데이트 할 수 있는지 여부를 선택합니다 해당 슬롯 목록이 있는지 여부를 감지
  • 트리 노드가 직접 트리에 삽입되어있는 경우
  • 리스트는 또한 각각의 대응하는 키 슬롯이 추가되는 경우
    • 8 개 이상을 추가 할 노드의 목록이 때 감지 할 수있다 나무를 수행 할 다음이 설명합니다 treeifyBin 결정 조건도 있기 때문에, 이것이 가능하다는 것을 유의
  • 마지막으로, 만약 크기> 용량 확장을위한 임계 값

여기에서 우리는 크기 조정 () 확장 코드와 treeifyBin () 코드 트리를 분석하기 위해 계속

    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        // 如果数组槽位小于 64 不进行树化,而是对 table 进行扩容
        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);
        }
    }
复制代码

이로부터 당신이 볼 수있는 하나의 연결리스트 데이터 테이블 8 레드 - 블랙 트리로 변환 할 때 또는 배열 (64) 의지의 용량과 동일, 그렇지 않으면, 크기 조정 () 확장을 수행하는 이동보다 더 큰이, 링크 된 목록은 하나의 마이그레이션 밖으로의 확장에 의해 더 이상 8보다 때.

final Node<K,V>[] resize() {
        // 重置之前暂记录之前数组桶的信息及相关配置信息
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        // 如果之前 table 中有数据的话
        if (oldCap > 0) {
            // 如果超出了最大容量值,设置 threshold 最大值
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 将之前的 table 大小扩大一倍作为新的数组桶的容量,当然不能超出最大值
            // 前提是之前 table 大小要大于默认值,不然数据量小没有扩容的必要直接使用默认值即可
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }

        else if (oldThr > 0) // 如果之前 table 中没有数据,将之前 table 的 threshold 作为新 table 的容量大小
            newCap = oldThr;
        else {               // 如果 oldCap 与 oldThr 之前都没有指定那么使用默认值创建,初始化创建 map 其实就是进入的这个分支
            newCap = DEFAULT_INITIAL_CAPACITY;
            // 装载因子 * 默认容量大小作为新的 threshold
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 如果新的 threshold == 0 使用新的容量大小重新计算
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
        }
        // 替换掉原先的 threshold 为新的值
        threshold = newThr;
        // 创建一个新的数组桶准备复制迁移数据
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        // 如果之前的 table 不为 null 开始迁移数据
        if (oldTab != null) {
            // 遍历之前的 table
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                // 处理不为 null 的数据
                if ((e = oldTab[j]) != null) {
                    // 将原 table 中的数据置为 null 便于断开其可能存在的引用链利于垃圾回收
                    oldTab[j] = null;
                    // 如果只有数组桶的一个数据,也就是槽位链表没有数据,这直接放入新的 table 槽位即可
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    // 如果节点是树节点 红黑树挡在单独章节分析 - TODO
                    // 如果链表结点数据小于 6 会将红黑树退化为链表
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // 处理 table 中槽位存在链表的情况并且不是树的情况,将原先的单个链表分化为 2 个链表
                        // 通过这段代码就避免了添加数据需要再次 hash puVal() 的低效率问题
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            // 低位存储在 loHead 中
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else { // 否则放入 hiHead 链表中也就是 原索引槽位 + oldCap
                                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;
    }
复制代码

이하 6의 레드 - 블랙 트리 노드 데이터, 링크 된 목록으로 변질 할 때, 우리가 +이 조건이 슬롯의 원래 위치에 배치 될 것이다, 또는 우리가 슬롯 인덱스를 넣어 만족이 코드 목록을 보면 oldCap에 위치, 그 이유는 무엇입니까?

if ((e.hash & oldCap) == 0) {
    if (loTail == null)
        loHead = e;
    else
        loTail.next = e;
    loTail = e;
}
复制代码

예를 들어, 여기서, 해시 (키-A는) = 경우 (14), 팽창시에 상기 캡은 확장의 2 배이기 때문에 (16)은 그 이전리스트 위치 14 슬롯 (16--1)는,이 위치 (14) = 캡 그것은 새로운 캡 = 32과 동일, 다음 링크 된 목록의 데이터 마이그레이션의 슬롯 (14) 전 슬롯을 할 필요가? 간단한 아이디어는

  • 이 같은 결과 인덱스 슬롯을 변경할 필요가없는 경우는 원래의 위치에 저장 될 수있다
  • 그렇지 않으면 마이그레이션됩니다
  • java8 전에 putVal ()는 해시 및 길이하고있는 호출 - 데이터를 저장하는 1 자바 8 최적화

14 (32--1) = 14 데이터 보면 마이그레이션 할 필요가없는 것을 나타낸다 e.hash 및 oldCap = 14 = 0 (16)은 원래의 위치, 여기서주의해야 할 점은 상기 캡 용량은 항상 2 ^ N 수단임을 나타낸다 단지 큰 데이터와 그 데이터보다 16, (예를 들어 17, 18, 19 .... N) = 16 (16) 할 것이며, 그래서 e.hash 16> = 16은 단지 현재의 확장 부이다 데이터 슬롯의 원래 위치가 + 16 상에 배치된다.

데이터 가져 오기
    public V get(Object key) {
        Node<K,V> e;
        // 先计算其 hash 值然后调用 getNode
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // 如果有数据的话
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
            // 如果槽位中的数据 hash 值和 key 的 hash 相等
            // 并且他们的 key 相等(== 和 equals)
            // 那么槽位中的数据就是目标数据直接返回即可
            if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 如果槽位中存在链表
            if ((e = first.next) != null) {
                // 如果是红黑树就去红黑树中找 -- TODO
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    // 遍历链表知道找到目标值后返回
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
复制代码
데이터를 삭제

그들은 알게하는 데이터를 삽입하고 데이터를 추가, 아주 간단한 실제로 데이터를 삭제하는 방법입니다

    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
                null : e.value;
    }
    
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        // 如果 table 中存在 key 对应的 hash 值
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            // 如果 key 就是对应槽位的 key 则找到数据
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            // 去槽位链表中查找
            else if ((e = p.next) != null) {
                // 如果是一个树去树节点中查找
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        // 遍历槽位链表查找对应的数据
                        if (e.hash == hash &&
                                ((k = e.key) == key ||
                                        (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            // 如果找到了 key 对应的值
            // 根据后续的判断确定是否需要删除对应的数据结点
            // 默认 remove, matchValue: false 需要进行删除
            if (node != null && (!matchValue || (v = node.value) == value ||
                    (value != null && value.equals(v)))) {
                // 如果是树节点则删除树中的结点

                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                // 如果是 table 槽位上的值,则将其下一个结点复制到槽位上
                else if (node == p)
                    tab[index] = node.next;
                // 如果在槽位链表上删除当前节点
                else
                    p.next = node.next;
                // 修改次数 + 1 用于迭代器 fail-fast
                ++modCount;
                // 数据长度 - 1
                --size;
                // 删除后要做的事情留个子类实现
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

复制代码
클리어지도
    public void clear() {
        Node<K,V>[] tab;
        // 修改次数 + 1
        modCount++;
        // 如果 table 不为空
        if ((tab = table) != null && size > 0) {
            // 重置 size 属性
            size = 0;
            // 遍历将每个槽位数据置位 null
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }

复制代码
이 값을 포함
    public boolean containsValue(Object value) {
        Node<K,V>[] tab; V v;
        // 如果 table 不为空
        if ((tab = table) != null && size > 0) {
            // 遍历 map 所有槽位
            for (int i = 0; i < tab.length; ++i) {
                // 遍历每个槽位链表如果找到则返回 true
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                    if ((v = e.value) == value ||
                            (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
    }
复制代码

추천

출처juejin.im/post/5d790540f265da03c128c532