[JavaSE 소스 코드 분석] HashMap에 대한 개인의 이해

무엇의 HashMap?

지도 데이터를 저장하기위한 일반적인 자바 키 - 값 구조입니다, 열쇠
HashMap의 맵 구조가 해시 알고리즘이 사용 된 기본 액세스 키 값

HashMap의 : Map 인터페이스의 해시 테이블을 구현합니다.

  1. 이 구현은 옵션의 맵 오퍼레이션을 모두 제공합니다. 이용 널 null 값과 키를 수
    (널 (null)이 아닌 동기 또한의 사용을 허용뿐만 아니라, HashMap의 해시 테이블 클래스는 거의 동일합니다.)
  2. 이 클래스는, 매핑의 순서를 보장하지 않습니다 특히, 그 순서를 영원히 지속되는 것은 아닙니다.
  3. 해시 함수 소자 가정이 구현 적절 기본 작업 (하세요 넣어) 안정된 성능을 제공하기 위해, 상기 터브 사이에 분포한다.
  4. 반복 성능이 너무 높은 초기 용량을 설정하지 않는 것이 중요 경우 - (값의 매핑 키) 따라서 비례. 반복 시간은 "용량"(버킷 수) 및 크기를 수집 HashMap의 예를 볼 수 있어야 (부하 계수가 너무 낮음)

HashMap의는 기본 데이터 구조를 무엇입니까?

보조 데이터 구조의 엔트리 맵이 기본 구조는 맵 항목의 배열
연결리스트 메모리 소자 충돌 사용하지만 충돌 사용 해시 연결리스트 처리 방법은
충돌이 어느 정도의 위치에 대한 데이터를 축적 블랙 트리로 변환 될 구조

// 必定是2的倍数 
transient Node<K,V>[] table;

데이터 구조를 부하의 HashMap는리스트를 기억 소자 어레이 인
경우보다하는 위치의 요소의 수와 같거나 8 ( 테이블 (64)의 크기보다 큰 경우 로 변환된다리스트) 때 레드 - 블랙 트리 구조 TREEIFY_THRESHOLD = 8
if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash)
요소의 개수는 레드 - 블랙 트리 6보다 작은 UNTREEIFY_THRESHOLD 연결리스트 구조 = 6으로 변환
if (lc <= UNTREEIFY_THRESHOLD) tab[index] = loHead.untreeify(map)

배열 기능은 다음과 같습니다 주소 를 쉽게 삽입삭제 문제
목록 : 특징은 주소 , 어려움을 삽입 하고 삭제 쉬운
레드 - 블랙 트리 : 자기 균형 이진 검색 트리를 검색 효율은 목록에서 효율을 보면, 매우 높은 O 링이 O (n)으로 감소된다 (logn)

  1. 목록의 복잡한 구조보다는 레드 - 블랙 트리 구조는, 몇 번 노드의 목록, 그것은 전체적인 성능에서 보인다, 배열의 구조는 + + 목록 레드 - 블랙 트리 반드시 연결리스트 +의 배열의 구조 성능보다 높을 수 없습니다
  2. 자주 확장을 해시 맵, 분할의 원인이 계속 매우입니다 레드 - 블랙 트리의 바닥을 구조 조정하는 시간이 많이 소요.
    따라서,리스트의 길이는 크게 효율성을 개선하는 레드 - 블랙 트리에 긴 시간이다

왜 테이블 용량는 2의 배수 여야합니다?

대신 모듈러 산술 연산에 의해 데이터 액세스 비트 용이하게하기 위해
해시를 및 (N-1)의 저장 위치에

// index 为元素在table数组中存放位置
// n = table.length
// hash 为key的hash
index = (n - 1) & hash;

// 有可能1都集中在前16位中 
// 而导致明明相差很大的数据 因为后16位相同而发生冲突
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 右位移16位, 正好是32bit的一半, 自己的高半区和低半区做异或, 
// 就是为了混合原始哈希码的高位和低位, 以此来加大低位的随机性. 
// 而且混合后的低位掺杂了高位的部分特征, 这样高位的信息也被变相保留下来. 

두 배수 작업을 수행하는 방법의 테이블 용량?

입력 파라미터 이상인 기기는 비트 연산 또는 오른쪽 방향으로 2의 최소 배수를 수득하고,

/**
* 返回大于等于给定参数的值(2的倍数)
* 首位为1 其余为0
* cap最大为: 1 << 30
*
* 先求全1 再加1 --> 1111 + 1 = 1 0000
*/
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;
}
  • 왜? (1) 작동 마이너스 모자 하시겠습니까
    이 방지되면, 캡 모자는 2의 거듭 제곱이있는 경우 (2)의 전력을 가지고 있으며,이 작업 마이너스 1을 수행하지 않습니다, 다음 몇 가지 부호없는 오른쪽 시프트 연산의 구현 뒤에 완료 반환이 용량은 2 배 캡 될 것입니다.
  • 입력 파라미터 확실히 1 비트 (1)를 갖고 있지 않기 때문에 0 최상위 비트가 1이고,
    오른쪽의 처음 : 최고 레벨 및 최고 레벨 1로 높은 시간 것이다
    번째 오른쪽 : 제 2 3 4 상단 두 것 1
    번째 권리 : 상위 4 전 8 상위 4와 5는 1이 될 것입니다
    ...
    모든 1 MSB 다음과 같습니다
  • 마지막 임계 값은 일시적으로 만 크기 조정을 주어진되어 저장된다 () 초기화
// initial capacity was placed in threshold
else if (oldThr > 0) 
    newCap = oldThr;

엔트리 구조는 무엇인가?

입력 단일 키 - 값 쌍은 동작의 값을 간단한 키를 제공

interface Entry<K,V> {
    // 返回该实例存储的Key
    K getKey();

    // 返回该实例存储的Value
    V getValue();

    // 替换该实例存储的value 
    // 返回原有value值
    V setValue(V value);

    // 判断两实例相等的方法
    // 一般指定两者的Key, Value均要相等
    boolean equals(Object o);

    // 获取该实例的hashCode
    // 一般为该实例的唯一标识
    int hashCode();

    // 返回一个比较Entry key值的比较器
    public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
        return (Comparator<Map.Entry<K, V>> & Serializable)
            (c1, c2) -> c1.getKey().compareTo(c2.getKey());
    }

    // 返回一个比较Entry value值的比较器
    public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
        return (Comparator<Map.Entry<K, V>> & Serializable)
            (c1, c2) -> c1.getValue().compareTo(c2.getValue());
    }

    // 通过给进比较key值的比较器 来获得一个比较Entry的比较器
    public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
        Objects.requireNonNull(cmp);
        return (Comparator<Map.Entry<K, V>> & Serializable)
            (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
    }

    // 通过给进比较value值的比较器 来获得一个比较Entry的比较器
    public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
        Objects.requireNonNull(cmp);
        return (Comparator<Map.Entry<K, V>> & Serializable)
            (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
    }
}

노드 : HashMap의 특정 구현에 진입

노드 완화 수단으로서 주목적 처리 해시 충돌이 링크리스트 노드 구조이다
(계산 된 해시 메모리 위치는 소자에 다음 노드로서, 소자에 다음 요소 그려져있다)는

static class Node<K,V> implements Map.Entry<K,V> {
    // hash, key一般赋值后不能被修改
    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; }

    // 该Node实例的hashCode是key的hashCode和value的hashCode相异或
    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

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

    // key,value都相等
    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;
    }
}

해시 충돌하는 접근 방식

  1. 오픈 주소
    충돌 일단은, 한 해시 테이블의 크기가 충분한으로, 빈 해시 주소는 항상 찾아 기록을 저장 할 수 있습니다, 다음 빈 해시 주소를 찾고 갔다
  2. 체인 어드레스 방법은
    노드의 링크 된리스트와 같은 해시 테이블의 각 셀 헤더 인덱스 요소 동의어의리스트를 구성. 모든 해시 주소
    즉 충돌 체인 유닛에 키를 넣어가 헤드 노드 목록의 꼬리
  3. 해싱 다시
    충돌 할 때까지 또 다른 해시 함수의 해시 어드레스 계산 기능 다른 주소 충돌이 될 때까지 발생하지 않을 때
  4. 일반적인 플로우 영역의 설정은
    두 부분 해시 테이블 기본 테이블과 오버 플로우 테이블로 분할되며, 상충하는 요소는 오버 플로우 테이블에 배치

HashMap의 초기화 또는 확장 크기 조정 ()

  1. 그것을 호출하는 시간이 초기화 시작할 때까지의 HashMap은 데이터를 추가 한 후 추가 용량 (임계 값에서) 필요 여부를 결정하기 시작합니다
  2. 매개 변수가 기본 맵 용량 임계 값으로 설정되지 않은 경우 (이 지점에서 제공) (16) (9)입니다
  3. 팽창, 임계 값은 용량을 두 배로 할 때
/**
* 初始化或数组容量翻倍
*/
final Node<K, V>[] resize() {
    // 获得原有全局表 table
    Node<K, V>[] oldTab = table;
    // 获得原有表的容量 
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // 获得原有表的阈值 容量*负载因子
    // 如果设置了初始容量 threshold等于设置的初始容量(大于等于输入参数的最小的二倍数)
    // 如果没有则为0
    int oldThr = threshold;
    // 设置新表的容量和阈值
    int newCap, newThr = 0;
    // 如果原有表不为空 
    if (oldCap > 0) {
        // 如果原有表的容量大于等于最大容量 不用扩展
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        // 如果旧表的容量 大于16 翻倍后小于最大容量
        } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 新表的阈值也是旧表阈值的两倍
            newThr = oldThr << 1; 
    // 如果旧表为空 并且事先设置了参数 threshold不为空
    } else if (oldThr > 0) 
        // 使用初始化的旧表阈值做新表的容量
        newCap = oldThr;
    // 旧表为空 没有设置参数 threshold为空
    else {
        // 新表容量 使用默认初始容量 16
        newCap = DEFAULT_INITIAL_CAPACITY;
        // 新表阈值为 16*0.75 = 12
        newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 以上就处理完 新表的参数 容量newCap
    // 此时 旧表为空 并且设置了参数 threshold不为空
    if (newThr == 0) {
        // 使用新表的容量计算新表的阈值
        float ft = (float) newCap * loadFactor;
        // 新表的容量小于最大容量 计算的新表阈值也小于最大容量 则获得新表阈值
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
                (int) ft : Integer.MAX_VALUE);
    }
    // 以上就处理完 新表的参数 容量newCap和阈值newThr
    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;
                // 如果该位置只有一个Node 没有下一Node
                if (e.next == null)
                    // 通过indexFor存入新表中
                    newTab[e.hash & (newCap - 1)] = e;
                
                // 判断该位置Node的链接Node是什么结构?
                // 树形结构 红黑树
                else if (e instanceof TreeNode)
                    ((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
                // 链状结构 链表
                else {
                    // head 指向链表头部 tail 构建整个链表
                    Node<K, V> loHead = null, loTail = null;
                    Node<K, V> hiHead = null, hiTail = null;
                    Node<K, V> next;
                    do {
                        // 先取到该位置的下一节点
                        next = e.next;
                        // e.hash & oldCap = 0 则该Node不需要移位
                        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;
}

계산 된 해시의 HashMap 소자

그들의 16 비트 높은 해시 코드 또는 이종
보다 균일하게 분포하도록 해시 이상의 개별 요소 때문에
우리는 비교적 작은 용량을 일반적으로는 ^ 16 (2)
위치의 값을 산출 키 요소 hash&(n-1)정보가 유효 해시 충돌하므로 대략 몇 낮음 큰
충돌 완화는 동작에서 포함되는 정보는 어느 정도 높게 할 수 있는지

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

높은 키 해시 값을 계산의 설계자도 이루어진다 (16 비트 XOR 연산 하이로 이렇게 및이 때의 계산법은 높은 결합 실제로 낮고 낮은) 랜덤를 증가시키는, 충돌 충돌의 가능성을 감소

/ 업데이트 요소를 추가 해시 맵

  1. 테이블이 크기를 조정 초기화 여부를 먼저 확인 () 초기화
  2. (N-1)을 취득한 저장 ​​위치 기억 소자의 위치는 해쉬가 비어있는 경우 및
  3. 핵심 요소는 해시 충돌의 값과 같은 발생, 꼬리 / 리프 노드에 요소를 추가하지 않습니다 위치를 찾을 수
  4. 이전 값을 반환 값 값을 업데이트 할 요소의 값을 동일하게 키를 찾기
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; 
    Node<K,V> p; 
    int n, i;
    // 如果当前表为空 还没创建 或者创建了 但表的大小为0
    if ((tab = table) == null || (n = tab.length) == 0)
        // 初始化表格
        n = (tab = resize()).length;
    // i = (n - 1) & hash 通过位运算得到需要存放的位置
    // 如果该位置为空 则直接存储
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 发生hash冲突
    else {
        // 获取存放位置的元素
        Node<K,V> e; 
        K k;
        // p 是已存放元素
        // 判断p是否与将要存放的元素key相等
        // 如果key相等 表示是更新元素
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // key不相等 并且p为树状结构节点
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // key不相等 并且p为链状结构节点
        else {
            for (int binCount = 0; ; ++binCount) {
                // 找到最后一个节点
                if ((e = p.next) == null) {
                    // 添加节点
                    p.next = newNode(hash, key, value, null);
                    // 如果该链表长度大于等于7 则转化为红黑树结构
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        treeifyBin(tab, hash);
                    break;
                }
                // 继续判断查询节点是否与将要存放的元素key相等
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 如果存放的位置不为空
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 如果表格元素个数大于阈值 则扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

HashMap의 값

  1. 테이블이 비어 분석하는 것은 공백 또는 null
  2. 해시 및 확인 (N-1) 널 (null)의 위치가 비어 반환 여부를 비어 있음
  3. 첫 번째 요소는 반환 요소의 요구 사항을 충족하는지 여부 결정
  4. 해시 충돌의 발생은, 요소가 반환됩니다 null이 발견되지 않는 반환있는 다른 노드의 위치를 ​​확인
public V get(Object key) {
    Node<K,V> e;
    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;
    // 当table不为空 并且hash位置上存有元素
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        // 检查该位置的第一个元素
        if (first.hash == hash &&
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        // 如果第一个元素 不符合查询条件
        // 则表示有可能是hash冲突 
        if ((e = first.next) != null) {
            // 如果首位元素是树节点
            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;
}

요소를 제거 해시 맵

  1. 테이블이 비어 분석하는 것은 공백 또는 null
  2. 는 getNode (해시 키) 같은 프로세스와 키 값에 기초하여 정규화 요소를 찾아
  3. 요소를 찾은 후이를 삭제하고 해당 요소를 반환
public boolean remove(Object key, Object value) {
    return removeNode(hash(key), key, value, true, true) != null;
}

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是否为空 并且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值 "getNode(hash, key)"
        // 首位元素
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        // 发生hash冲突 该位置其他节点
        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);
            }
        }
        // 找打符合要求的元素node
        // matchValue 表示是否需要匹配value值 false表示不用 即使输入value不对 也可以删除该元素
        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);
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

HashMap에 왜 스레드 안전하지?

이 때문에 방법은 스레드 안전하지 않습니다

HashMap의 동시성 시나리오에서 어떤 문제가있을 수 있습니다?

데이터 손실 데이터가 죽음의 사이클을 반복

  • 더그 레아의 글 :

상기 관통 Java7 동시에이 명령 테이블을 실행하는 두 개의 스레드가있는 경우, 데이터 손실이 이유 [내가] = 엔트리는 두 개의 스레드 수도권을 만들 때 널 소스 코드 분석에 저장된 데이터의 손실이있을 수 있도록.

두 개의 스레드가있는 경우 자신이 키가 존재하지 않는 것을 발견하고, 두 개의 스레드가 실제로 같은 키, 때 자신의 전자 항목에 대해 제공되는 첫 번째 스레드에 기록 목록, 두 번째 스레드 실행에 e.next에,이 시간은 여전히 보유 데이터가 나타난 목록에 삽입 소유, 마지막 노드를 얻었다 数据重复.

소스 코드를 넣어 통해 찾을 수 있습니다, 데이터는 먼저지도에 기록 된 다음 요소의 크기를 조정에 따라 번호를 수행할지 여부를 결정할 수 있습니다.
더 까다로운 문제는 그 과정에서 무한 루프 크기를 조정할 수있을 것입니다.

해시 맵은 연결리스트 처리의 역순 과정의 크기를 조정하기 때문에 그 이유는 주로
두 스레드가 동시에 크기를라고 가정하고, 그 과정에서 제 스레드 A-> B가 상대적으로 느리고, 상기 제 스레드 역방향 프로그래밍 완료 BA는 B. 그래서 CPU 사용량 급증이있을 것이다, B-> A-> 사이클을 보였다.

PS : 찾을 수있다이 방법에서, 자바 8의 목록에 대한 역 과정을 사용하여 역방향리스트되었는지 주로 인해 무한 루프의 결과이고, 무한 루프 문제가 크게 개선되었다.

디버그 더 HashMap에 의해 이해 될

환경

  • 하게 IntelliJ IDEA 2018 프로페셔널 에디션
  • Deepin 리눅스 15.9 데스크탑

방법

테스트 코드

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Random;

/**
 * 通过debug查看HashMap存储数据时的结构
 *
 * @author lingsh
 * @version 1.0
 * @date 19-9-25 下午4:29
 */

public class TestHashMap {
    public static void main(String[] args) {
        // size 存放数据量
        // cap HashMap初始设置容量 10 --> 16
        int size = 10000, cap = 10;
        Map<THMString, Integer> map = new HashMap<>(cap);
        // 存放数据
        for (int i = 0; i < size; i++) {
            map.put(THMString.getRandomString(), i);
        }
        // 用于定点调试(只是该main函数的终止位置, 可以用于暂停查看map结构)
        System.out.println(map.size());
    }
}

/**
 * TestHashMapString 
 * hashCode分布极其集中的自定义类
 * 底层是LEN大小的字符串
 */
class THMString {
    /**
     * 底层真实数据
     */
    private String str;
    /**
     * SIZE 用于加强实例碰撞的可能性
     */
    private final static int SIZE = 1024;
    /**
     * 底层数据大小
     */
    private final static int LEN = 5;
    /**
     * 使用随机数来确定底层数据内容
     */
    private static Random random = new Random();

    public THMString(String str) {
        this.str = str;
    }

    @Override
    public int hashCode() {
        // 集中hashCode 通过不断的取余来加强碰撞可能
        return str.hashCode() % SIZE % SIZE % SIZE % SIZE % SIZE;
    }

    @Override
    public boolean equals(Object obj) {
        THMString thms = (THMString) obj;
        return Objects.equals(this.str, thms.str);
    }

    @Override
    public String toString() {
        return "[String:" + str + "\thashCode:" + hashCode() + "]";
    }

    /**
     * 获取随机的实例作为HashMap的Key
     * 
     * @return 随机的实例
     */
    static THMString getRandomString() {
        char[] chars = new char[LEN];
        for (int i = 0; i < chars.length; i++) {
            int word = random.nextInt('z' - 'a');
            chars[i] = (char) (word + 'a');
        }
        return new THMString(new String(chars));
    }
}

목표 달성

내부 데이터 구조
  • 마디
  • TreeNode를
확장 과정
  • 처음에 확장

  • 확장 모바일 노드에 대한 새 테이블

디버그 팁

디버그 IDEA 폐쇄 클래스 구조 최적화

다음, newThr 등 :이 옵션은 같은 IDEA 숨어 매개 변수를 켜면

찾을 경로 테이블 저장 항아리

소스 코드의 디버그뿐만 아니라 우리의 개인적인 사용되는 HashMap에서, 시스템이 사용하는 프로그램을 실행, 그래서 우리는 초기화시 HashMap에 액세스 시스템 호출 값을 잡을 수 있기 때문에
따라서, 제안

  • 자신의 절차는 중단 점을 설정 줘 중단 점을 자신의 프로그램을 시작하고 설정하기로 결정하는 HashMap의 소스

참조 기사

  1. 완벽한 해설을 해시 맵
  2. 자세한은 HashMap의 크기를 조정
  3. 해시 ()의 정적 소스 코드를 주석 툴의 HashMap 방법 tableSizeFor () (ⅳ)
  4. HashMap의 매우 간단한 분석 [원본]는
  5. HashMap의 소스 코드 분석 (jdk1.8, 당신이 읽을 수 있도록하기 위해)
  6. 해시 맵 스레드 불안은 어디에 반영됩니다?
  7. / 오프보기 컬렉션에 아이디어 디버그 (의 HashMap, ArrayList를 등) 시운전

추천

출처www.cnblogs.com/slowbirdoflsh/p/11585463.html