Hashmap 소스 코드 분석 (jdk 1.8) 및 면접 문제

24315 단어 자바
Hashmap 소스 코드 분석
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import sun.misc.SharedSecrets;
  • Hash table 은 Map 인터페이스 에 기반 한 실현 이다.Hashmap 는 선택 할 수 있 는 맵 작업 을 제공 하고 키 와 값 을 null 로 허용 합 니 다.
  • Hashmap 의 실례 는 그 성능 에 영향 을 주 는 두 가지 매개 변수 가 있다. 초기 용량 과 적재 인자, 용량 은 해시 표 의 통 수량 이 고 초기 용량 은 해시 표 가 생 성 될 때의 용량 이 며 적재 인 자 는 해시 표 가 용량 이 자동 으로 증가 하기 전에 얼마나 많은 척도 에 도달 할 수 있 는 지 이다.해시 표 의 실체 수량 이 로드 인자 * 용량 을 초과 할 때 이 해시 표 에 대해 rehash 작업 (즉 내부 데이터 구 조 를 재 구축 하 는 것) 을 해 야 합 니 다. 해시 표 는 약 두 배의 통 수 를 확대 할 것 입 니 다.
  • 기본 규칙 은 기본 로드 인자 가 0.75 로 시간 과 공간 원가 사이 에 좋 은 절충 을 형성 했다.
  • 초기 용량 을 설정 할 때 맵 에 필요 한 항목 수 와 로드 인 자 를 고려 하여 rehash 작업 횟수 를 최대한 줄 여야 합 니 다.초기 용량 이 최대 항목 수 를 로드 인자 로 나 누 면 rehash 작업 이 일어나 지 않 습 니 다.
  • HashMap 인 스 턴 스 에 저장 할 맵 이 많 으 면 해시 표를 만 들 때 충분 한 초기 용량 저장 효율 을 사용 하면 필요 에 따라 표 의 크기 를 늘 리 고 자동 으로 다시 산열 할 것 입 니 다.
  • Hashmap 는 스 레 드 가 안전 하지 않 습 니 다. 여러 스 레 드 가 동시에 해시 맵 을 방문 하고 최소한 하나의 스 레 드 가 맵 구 조 를 수정 하려 면 외부 에서 동기 화 작업 을 해 야 합 니 다.(구조 수정 은 하나 이상 의 맵 을 추가 하거나 삭제 하 는 작업 입 니 다. 이미 존재 하 는 인 스 턴 스 의 키워드 와 관련 된 값 을 바 꾸 는 것 은 구조 수정 이 아 닙 니 다.)
  • fast - fail 이벤트: 여러 스 레 드 가 Collection 을 조작 할 때 그 중의 한 스 레 드 가 iterator 를 통 해 집합 을 옮 겨 다 닐 때 이 집합 내용 은 다른 스 레 드 에 의 해 변 경 됩 니 다.Concurrent ModificationException 이상 을 던 집 니 다.따라서 동시 수정 이 있 을 때 교체 기 는 빠르게 실패 할 수 있다.교체 기의 빠 른 실패 행 위 는 보장 되 지 않 습 니 다. 일반적으로 동기 화 되 지 않 은 병행 수정 이 존재 할 때 어떠한 단호 한 보증 도 할 수 없습니다.빠 른 실패 교체 기 는 Concurrent ModificationException 을 던 지기 위해 최선 을 다 합 니 다.따라서 이 이상 한 프로그램 에 의존 하 는 방법 은 잘못 되 었 습 니 다. 정확 한 방법 은 교체 기의 빠 른 실패 행 위 는 프로그램 오 류 를 검사 하 는 데 만 사용 되 어야 합 니 다.
    public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    private static final long serialVersionUID = 362498820763181265L;
    //       16,   2  (   ?    )
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    //    ,                  ,               
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //      
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //      ,                       ,         
    static final int TREEIFY_THRESHOLD = 8;
    static final int UNTREEIFY_THRESHOLD = 6;
    static final int MIN_TREEIFY_CAPACITY = 64;
  • 하 쉬 통 결점 의 정의
  •  static class Node implements Map.Entry {
            final int hash;//key hash ,           
            final K key;
            V value;
            Node next;
    
            Node(int hash, K key, V value, Node 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;
            }
        }
  • 계산 key. hashCode (), 고위 연산 h = key. hashCode () 는 32 비트 로 이 32 비트 의 높이 16 을 낮은 16 비트 에 위치 하여 이 또는 연산 을 하고 고저 bit 는 모두 연산 에 참여 합 니 다.이 진 연산 은 모 연산 보다 효율 이 높다.
  • static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
  • Hashmap 의 대 삼 구조 함수 분석: 초기 용량 이 0 보다 작 거나 로드 인자 가 0 보다 작 으 면 IllegalArgument Exception 이상 설정 의 초기 용량 이 최대 용량 보다 클 때 초기 용량 을 최대 용량 으로 설정 합 니 다.
  • 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);
        }
  • get () 은 특정한 key 값 에 따라 value 를 되 돌려 줍 니 다.hash (key) 를 통 해 key 의 해시 값 hash 를 계산 하고 (n - 1) & hash 는 해당 배열 의 위 치 를 얻 습 니 다.(n 은 배열 의 길이) 먼저 첫 번 째 노드 의 hash 값 과 key 가 계 산 된 key 의 hash 값 과 key 가 같 는 지 비교 하고 같 으 면 첫 번 째 노드 로 돌아 갑 니 다.그렇지 않 으 면 링크 의 다른 결점 을 비교 해 보 세 요.
  • public V get(Object key) {
            Node e;
            return (e = getNode(hash(key), key)) == null ? null : e.value;
        }
    final Node getNode(int hash, Object key) {
            Node[] tab; Node first, e; int n; K k;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
                // always check first node
                if (first.hash == hash && 
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                if ((e = first.next) != null) {
                /**        TreeNode   ,        ,       ,            */
                    if (first instanceof TreeNode)
                        return ((TreeNode)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;
        }
  • put () 는 지정 한 건설 과 값 을 map 와 연결 하여 이 키 값 을 map 에 넣 을 것 입 니 다.맵 에 원래 이 키 의 맵 이 있 으 면 오래된 값 을 바 꿉 니 다.오래된 값 을 되 돌려 주거 나 null 을 되 돌려 줍 니 다.
  • public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    /**onlyIfAbsent   true,           */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node[] tab; Node p; int n, i;
            /**          ,  table      ,  table    0, resize()     */
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            /**  table      null           ,              */
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
           /**         ,           (1)         ,     ;(2)       ,  putTreeVal();(3)           */
            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);
                            //              TREEIFY_THRESHOLD-1,       
                            if (binCount >= TREEIFY_THRESHOLD - 1)
                                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        HashMap           ,           
            ++modCount;
            /**      HashMap               , rehash,  */
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
  • resize () 는 table 의 길 이 를 초기 화하 거나 배로 늘 립 니 다. 2 차 멱 확장 배열 크기 이기 때문에 각 통 에 있 는 요소 의 위치 나 원래 와 같은 색인 또는 2 의 멱 거 리 를 새로운 table 로 이동 합 니 다.
  • 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;
                }
             //      2 newCap = oldCap << 1
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }
            // initial capacity was placed in threshold
            else if (oldThr > 0) 
                newCap = oldThr;
            // zero initial threshold signifies using defaults
            else {               
                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[] newTab = (Node[])new Node[newCap];
            table = newTab;
            //   bucket      buckets 
            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;
    /**   ,    ?   hash     01010,
    *oldCap 10000,01010&10000 = 00000,  newCap = oldCap<<1 = 100000,
    *  newCap-1  hash      11111&01010 = 00000,       */
                                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);
                            //      bucket 
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;
                            }
                            //    +oldCap  bucket 
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            return newTab;
        }

    면접 문제
    1. HashMap 의 원리 와 내부 데이터 구조?밑 에 해시 표 (배열 에 링크 추가) 를 사용 하고 링크 가 너무 길 면 (노드 가 8 개 에 달 함) 빨간색 과 검은색 나무 로 전환 하여 O (logn) 시간 복잡 도 에서 찾 을 수 있 습 니 다.2. HashMap 에서 put 방법의 과정 은?(1) 키 에 대해 해시 에 게 테이블 에 있 는 색인 위 치 를 계산 합 니 다.(2) 부 딪 히 지 않 으 면 통 에 넣 기;(3) 부 딪 히 면 링크 의 방식 으로 링크 의 뒤에 놓는다.(4) 링크 의 길이 가 한도 값 8 을 초과 하면 링크 를 빨간색 과 검은색 나무 로 전환한다.(5) 결점 이 이미 존재 한다 면 새 값 으로 낡은 값 을 교체 하고 낡은 값 을 되 돌려 줍 니 다.(6) size > threshold 라면 resize 해 야 합 니 다.3. hash 함수 의 실현?높 은 16 위 는 변 하지 않 고 낮은 16 위 와 높 은 16 위 는 다르다.그리고 (n - 1) & hash 가 아래 표 시 를 받 았 습 니 다.
  • 잘 하 는 글:http://www.importnew.com/20386.html
  • 좋은 웹페이지 즐겨찾기