JDK 1.8 HashMap put 소스 코드 분석

13500 단어 자바
가장 간단 한 예 에서 hashmap 의 소스 코드 를 분석 하기 시작 하 다.
Map map = new HashMap<>();
for (int i = 0; i < 10; i++) {
     map.put(i, i);
 }

내부 에 무슨 일이 있 었 습 니까?넣 는 방법 을 따라 들 어가 보도 록 하 겠 습 니 다.
public V put(K key, V value) {
 	return putVal(hash(key), key, value, false, true);
}

hash (key) 이 방법 은 무엇 입 니까?계속 해시 따라 들 어가 볼 게 요.
static final int hash(Object key) {
 	int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

어떻게 보면 잘 모 르 겠 어 요. (h = key. hashCode () ^ (h > > 16) 이 목적 은 무엇 입 니까?
OK, key. hashCode () 에 대응 하 는 것 은 Integer 의 hashcode 입 니 다. 그러면 Integer 의 hashcode 는 무엇 입 니까?이어서 Integer 가 어떻게 다시 쓴 hashcode 인지 보 세 요.
@Override
public int hashCode() {
    return Integer.hashCode(value);
}

public static int hashCode(int value) {
  	return value;
}

Soga, Integer 의 hashcode 가 int 의 값 이 었 구나, OK, 그럼 지금 h = key. hashCode () 의 값 을 알 수 있 겠 구나.
그런데 왜 h = key. hashCode () 는 그것 의 낮은 16 비트 (h > > > 16) 와 다른 가요?이 뒤에 다시 이야기 하 자. 우 리 는 이 의문 을 가지 고 계속 내 려 다 보 자.
자, 우 리 는 지금 hash 라 는 방법 을 다 본 척 하고 hash 값 을 되 돌려 주 었 다 는 것 을 알 고 있 습 니 다. 사실은 정수 입 니 다.
이제 PutVal 의 입 참 을 살 펴 보 겠 습 니 다.
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

미리 말씀 드 리 지만 뒤의 두 매개 변 수 는 우리 가 지금 관심 을 가 질 필요 가 없 기 때문에 보이 지 않 는 척 합 니 다.
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node[] tab; Node 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);
        else {
        	   ……
        }
        ……
 }

우 리 는 먼저 가장 간단하게 볼 때 뒤의 지금 이 예 도 갈 수 없 기 때문에 그것 이 공기 라 고 생각 하고 그것 이 아니 라 공기 라 고 생각한다.
우선, 첫 번 째 if (tab = table) = = null | (n = tab. length) = 0), table = = null 의 경 우 는 하나 입 니 다. 바로 map 가 new 에 나 온 후에 put 데이터 가 없습니다. hashmap 의 구조 함 수 를 보 세 요. table 을 초기 화하 지 않 았 습 니 다. 그 렇 죠?PS: (n = tab. length) = = 0 이 동쪽 은 도대체 언제 촉발 되 는 거 야??관련 소스 코드 를 다 봤 는데 도 발견 하지 못 해서 기분 나 빠...설마 노 는 거 라 고 쓰 여 있 는 건 아니 겠 지?.............................................................
OK, 이제 hashmap 의 초기 화 는 resize 라 는 방법 에 통합 되 었 다 는 것 을 알 게 되 었 습 니 다.
이 어 두 번 째 if (p = tab [i = (n - 1) & hash] = = null) 를 살 펴 보 자. 어떻게 보면 이게 무슨 귀신 인지. 그럼 우리 먼저 맨 안쪽 (n - 1) & hash 부터 말 하 자. 이것 이 바로 hash% (n - 1) 다. 아래 를 보 자.
a = 7, b = 14, c = 8
  ,   a % c  b % c,     a & (c - 1)   b % (c - 1)
7 & (8 - 1)
0111
0111
-------
0111
=7

14 & (8 - 1)
1110
0111
------
0110
=6

어떤 규칙 을 알 아 냈 는 지 그 정 수 는 2 의 N 제곱 - 1 을 제외 하고 최고 위 를 0 으로 바 꾸 고 낮은 위 치 를 모두 1 로 바 꾸 는 것 이다. 그러면 이때 모든 수 A & (2 ^ N - 1) 가 얻 은 결 과 는 A 의 낮은 N 자리 이다.
PS: 이제 왜 h ^ h > > 16, 낮은 16 위 와 높 은 16 위 가 다른 지 아 시 죠?그것 은 바로 16 위의 변 화 를 16 위 에 반응 하 게 하 는 것 이다. 그래 야 극단 적 인 상황 에서 대량의 hash 충돌 이 발생 하 는 것 을 방지 할 수 있다!
그래서 tab [i = (n - 1) & hash] 의 뜻 이 뚜렷 합 니 다. 위치 한 통 = = null 일 때 이 통 이 비어 있 고 데 이 터 를 넣 을 수 있다 는 것 을 설명 합 니 다. 마지막 으로 tab [i] = new Node (hash, key, value, null)
다음은 충돌 을 어떻게 처리 하 는 지 분석 하고 케이스 를 추가 하 겠 습 니 다.
Map map = new HashMap<>();
Random random = new Random();
for (int i = 0; i < 1000000; i++) {
   map.put(random.nextInt(10000000), random.nextInt(1000000));
}

이제 드디어 puutVal 의 소스 코드 를 맞 출 수 있 게 되 었 습 니 다.
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node[] tab; Node 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);
        else {
            Node 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)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;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

OK, 드디어 충돌 이 발생 했 습 니 다. 첫 번 째 if (p. hash = = hash & & & (k = p. key) = = key | | (key! =null && key.equals(k))))
  • p. hash = = hash & & p. key = = key 는 hash 가 같은 상황 에서 key 도 다 를 수 있다 는 것 을 설명 한다.
  • ((k = p.key) == key || (key != null & & key. equals (k))), key 가 복합 유형의 대상 이 라면 equals 방법 으로 동일 여 부 를 판단 해 야 하 며, equals 는 자 유 롭 게 재 작성 할 수 있 습 니 다.

  • 질문: put key 후 key 의 hash 값 을 바 꾸 면 어떻게 됩 니까?
    같은 키 를 반복 하면 맨 아래 에 있 는 if 를 봅 시다.
    if (e != null) { // existing mapping for key
        V oldValue = e.value;
        if (!onlyIfAbsent || oldValue == null)
            e.value = value;
        afterNodeAccess(e);
        return oldValue;
    }
    

    하면, 만약, 만약...null, 그럼 값 을 업데이트 해 야 하 는 지 판단 하 시 겠 습 니까?PS: after Node Access 와 after Node Insertion 은 큰 도움 이 되 지만 지금 은 또 잃 어 버 려 도 소 용이 없습니다.
    이 어 두 번 째 if if (p instanceof TreeNode) 를 분석 해 이 통 이 빨 간 검 은 나무 노드 인지 아 닌 지 를 판단 한다. 그렇다면 XXX, 이제 putTreeVal 이라는 방법 을 들 어가 보 자.
    이 어 마지막 else 를 분석 했다. 이 통 이 링크 라면...
  • 링크 끝 에 옮 겨 다 니 는데 이때 노드 수가 7 (TREEIFYTHRESHOLD - 1) 체인 시 계 를 붉 은 검 은 나무 로 바 꿉 니 다.
  • 을 옮 겨 다 니 는 과정 에서 같은 key 가 있 음 을 발견 하면 옮 겨 다 니 는 것 을 멈 추고 if (e! =저기

  • OK, put 의 방법 은 이미 끝까지 분석 되 었 습 니 다. 두 개 만 남 았 습 니 다. 하 나 는 + modCount 입 니 다. 이것 은 도대체 무엇 입 니까?하 나 는 resize ()
    알 겠 습 니 다. 먼저 + modCount 를 말씀 드 리 겠 습 니 다. 여기 서 100 W 자 를 생략 하 겠 습 니 다. 제 요리 가 아니 라 다른 지식 을 이야기 할 때 말씀 드 리 겠 습 니 다.
    마지막 으로 see resize 라 는 방법 을 보 겠 습 니 다. 재 미 있 고 많은 점 을 이 끌 어 낼 수 있 습 니 다.
    new Cap 과 new Thr 를 어떻게 재 계산 하 는 지 분석 해 보 겠 습 니 다.
    final Node[] resize() {
            Node[] 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;
           ……
        }
    

    어떻게 한눈 에 볼 수 있 는 지, WC, 두 개의 매개 변 수 를 다시 계산 해서 나 에 게 이렇게 큰 단락 을 만들어 줘...입 속으로 툭툭 털 었 지만 몸 은 성실 하 게 내 려 다 보 았 다..................................................
    사실 이것 은 resize 방법 으로 초기 화 하 는 사고 가 아 닙 니 다.
    다음은 int oldCap = (oldTab = = null)?0 : oldTab.length
    이 단 계 는 초기 화 를 위 한 것 입 니 다. 만약 oldCap = 0 이 라면 첫 번 째 if 판단 을 건 너 뛰 고 이 몇 가지 상황 을 분석 하 겠 습 니 다.
  • oldCap = 0, oldThr = 0, 이러한 상황 은 new HashMap < > () 로 인 한 것 입 니 다. 초기 용량 을 지정 하지 않 으 면 threshold 를 초기 화 하지 않 기 때 문 입 니 다.그리고 마지막 else 에 뛰 어 들 면 기본 값
  • 을 초기 화 합 니 다.
    newCap = DEFAULT_INITIAL_CAPACITY;//16
    newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//0.75 * 16
    
  • oldCap = 0, oldThr > 0. 이런 상황 은 new HashMap (n) 으로 인해 발생 한 것 입 니 다. 초기 화 용량 을 지정 하면 한도 값 을 초기 화 합 니 다. 한도 값 을 초기 화 할 때 재 미 있 는 방법 이 있 습 니 다. 여기 서 분석 하지 않 겠 습 니 다. 이 방법의 기능 은 cap 에서 가장 가 까 운 2 의 N 제곱 정 수 를 되 돌려 주 는 것 입 니 다. 예 를 들 어 cap = 6, 8
  • 으로 돌아 가 는 것 입 니 다.
    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;
    }
    
  • oldCap > 0, oldThr = 0 또는 oldThr > 1, 이러한 상황 은 put 데이터 입 니 다. 그러면 다음 과 같은 판단 을 하 겠 습 니 다.
  • 만약 oldCap > MAXIMUMCAPACITY (1 < < 30), 더 이상 확장 하지 않 고 threshold = Integer. MAXVALUE

  • 만약 oldCap * 2 < MAXIMUMCAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY 는 새로운 newThr = oldThr < < 1
    if (newThr == 0) {
      float ft = (float)newCap * loadFactor;
      newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                (int)ft : Integer.MAX_VALUE);
    }
    

    이 부분 은 바로 oldCap < 16 시 상황 을 처리 하 는 것 입 니 다.
    OK, new Cap 과 new Thr 를 다시 계산 하여 분석 하 였 습 니 다. 다음은 새로운 공간 을 분배 하 는 것 입 니 다.그리고 다시 카드 를 섞 습 니 다.
    final Node[] resize() {
            ……
            @SuppressWarnings({"rawtypes","unchecked"})
            Node[] newTab = (Node[])new Node[newCap];
            table = newTab;
            if (oldTab != null) {
                for (int j = 0; j < oldCap; ++j) {
                    Node 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)e).split(this, newTab, j, oldCap);
                        else { // preserve order
                            Node loHead = null, loTail = null;
                            Node hiHead = null, hiTail = null;
                            Node 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;
        }
    

    put 와 차이 가 많 지 않 습 니 다. 모두 3 부작 입 니 다. 보통 node 이거 나 빨 간 검 은 나무 이거 나 링크 입 니 다.
  • 일반적인 node 처리, new Tab [e. hash & (new Cap - 1)] = e, 다시 카드 를 세탁 한 셈 이다.
  • 붉 은 검 은 나무, 여 기 는 1 억 글자 가 생략 되 어 있 습 니 다.
  • 링크, 이것 은 비교적 재 미 있 습 니 다. 구체 적 으로 주석
  • 을 보십시오.
    // lo   A,hi   B
    Node loHead = null, loTail = null;
    Node hiHead = null, hiTail = null;
    Node next;
    do {
        //       
        next = e.next;
        //oldCap 2 N  ,          0
        //  e.hash&oldCap         01   ?
        //                  AB    ! 
        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);
    //     A    
    if (loTail != null) {
        loTail.next = null;
        newTab[j] = loHead;
    }
    //j + oldCap     B  ,              ?
    if (hiTail != null) {
        hiTail.next = null;
        newTab[j + oldCap] = hiHead;
    }
    

    질문: 높 고 hashmap 을 보 내 면 어떤 상황 이 발생 합 니까? (1.7 과 1.8), 답 부터 말 하면 1.7 과 1.8 모두 XXX 가 발생 할 수 있 지만 발생 하 는 위 치 는 다르다.
    자, put 방법 은 여기까지 만 간단히 말씀 드 리 겠 습 니 다.문제 가 있 거나 다른 견해 가 있 는 사람 은 토론 을 환영 합 니 다!저 는 각종 난치병 을 전문 적 으로 맡 고 있 지만 치료 하지 않 습 니 다. 하하 하.

    좋은 웹페이지 즐겨찾기