면접 필수: LinkedHashMap 소스 코드 분석 (JDK 8)

장 욱 동의 블 로그http://blog.csdn.net/zxt0601 gayhub 와 저 gaygayup: [mcxtzhang 의 Github 홈 페이지]https://github.com/mcxtzhang
개술
윗글 에서 우 리 는 이미 이 야 기 를 나 누 었 는데 HashMap 이 편 은 윗글 의 기초 위 에 있다.따라서 위의 글 을 보지 못 했다 면 면접 필수: HashMap 소스 코드 분석 (JDK 8) 본 고 는 몇 가지 일반적인 방법 으로 시작 하여 LinkedHashMap 의 소스 코드 를 읽 을 것 입 니 다.구조 방법 - > 상용 API (증가, 삭제, 수정, 검사) 의 순서에 따라 원본 코드 를 읽 고 읽 기 방법 에 관련 된 변수의 의 미 를 설명 합 니 다.LinkedHashMap 의 특징 을 파악 하고 장면 을 적용 한다.
만약 본문 에 정확 하지 않 은 결론, 견해 가 있다 면 여러분 은 저 와 토론 하고 함께 발전 하 는 것 을 제기 해 주 십시오. 감사합니다.
2 개요
요약 하면 LinkedHashMap 은 관련 배열, 해시 표 입 니 다. 스 레 드 가 안전 하지 않 고 key 를 null 로 허용 합 니 다. value 는 null 입 니 다.그것 은 HashMap 에서 계승 하여 Map 인 터 페 이 스 를 실현 했다.그 내부 에 양 방향 링크 도 유지 하고 데 이 터 를 삽입 하거나 데 이 터 를 방문 하거나 수정 할 때마다 노드 를 증가 하거나 링크 의 노드 순 서 를 조정 합 니 다.교체 시 출력 순 서 를 결정 합 니 다.
기본 적 인 상황 에서 옮 겨 다 닐 때의 순 서 는 노드 를 삽입 하 는 순서에 따른다.이것 도 HashMap 와 가장 큰 차이 이다.또한 구조 할 때 accessOrder 파 라 메 터 를 전송 하여 옮 겨 다 니 는 순서 로 방문 하 는 순서에 따라 출력 할 수 있 습 니 다.
계승 자 HashMap 이기 때문에 HashMap 상기 에서 분석 한 특징 은 수출 이 무질서 한 것 을 제외 하고 다른 LinkedHashMap 이 모두 있다. 예 를 들 어 확장 전략, 하 쉬 통 의 길 이 는 반드시 2 의 N 순서 등 이다.LinkedHashMap 실현 할 때 오 버 라 이 드 를 다시 쓰 는 몇 가지 방법 이다.출력 시퀀스 의 질서 있 는 수 요 를 만족 시 킵 니 다.
예제 코드:
이 인 스 턴 스 코드 에 따라 먼저 현상 LinkedHashMap 의 특징 을 살 펴 보 자. 데 이 터 를 삽입 하거나 방문 하거나 데 이 터 를 수정 할 때마다 노드 를 증가 하거나 링크 의 노드 순 서 를 조정 한다.교체 시 출력 순 서 를 결정 합 니 다.
        Map map = new LinkedHashMap<>();
        map.put("1", "a");
        map.put("2", "b");
        map.put("3", "c");
        map.put("4", "d");

        Iterator> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        System.out.println("   accessOrder=true   :");

        map = new LinkedHashMap(10, 0.75f, true);
        map.put("1", "a");
        map.put("2", "b");
        map.put("3", "c");
        map.put("4", "d");
        map.get("2");//2           
        map.get("4");//4     
        map.put("3", "e");//3     
        map.put(null, null);//         null
        map.put("5", null);//5
        iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

출력:
1=a
2=b
3=c
4=d
   accessOrder=true   :
1=a
2=b
4=d
3=e
null=null
5=null

3 노드LinkedHashMap 의 노드 EntryHashMap.Node 로부터 계승 되 어 그 기초 위 에서 확장 되 었 다.양 방향 링크 로 바 뀌 었 습 니 다.
    static class Entry extends HashMap.Node {
        Entry before, after;
        Entry(int hash, K key, V value, Node next) {
            super(hash, key, value, next);
        }
    }

이 동시에 클래스 에 두 개의 구성원 변수 head tail 가 있 는데 각각 내부 양 방향 링크 의 표 두, 표 꼬리 를 가리킨다.
    //        
    transient LinkedHashMap.Entry head;

    //        
    transient LinkedHashMap.Entry tail;

4 구조 함수
    //   false,                 。  true,                。
    // true ,            LruCach
    final boolean accessOrder;
    
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }
    //         ,
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }
    //         ,        
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }
    //         ,        ,           
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }
    //     Map    ,
    public LinkedHashMap(Map extends K, ? extends V> m) {
        super();
        accessOrder = false;
        //        ,      map            。
        putMapEntries(m, false);
    }
    

소결: 구조 함수 가 HashMap 에 비해 하나의 accessOrder 매개 변 수 를 증가 시 켰 다.교체 시의 노드 순 서 를 제어 하 는 데 사용 합 니 다.
5 증가LinkedHashMap 어떤 put 방법 도 다시 쓰 지 않 았 다.그러나 새로운 노드 를 구축 하 는 newNode() 방법 을 다시 썼 습 니 다. newNode()HashMapputVal() 방법 에서 호출 되 고 putVal() 방법 은 데이터 putMapEntries(Map extends K, ? extends V> m, boolean evict) 를 대량으로 삽입 하거나 하나의 데이터 public V put(K key, V value) 를 삽입 할 때 호출 됩 니 다.LinkedHashMap 재 작성 newNode() 은 새로운 노드 를 구축 할 때마다 linkNodeLast(p); 을 통 해 새로운 노드 를 내부 양 방향 링크 의 끝 에 연결 했다.
    //       ,    `LinkedHashMap.Entry`    `Node`.
    Node newNode(int hash, K key, V value, Node e) {
        LinkedHashMap.Entry p =
            new LinkedHashMap.Entry(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }
    //      ,        
    private void linkNodeLast(LinkedHashMap.Entry p) {
        LinkedHashMap.Entry last = tail;
        tail = p;
        //       
        if (last == null)
            head = p;
        else {//            
            p.before = last;
            last.after = p;
        }
    }

그리고 HashMap 전문 적 으로 LinkedHashMap 에 게 남 겨 진 afterNodeAccess() afterNodeInsertion() afterNodeRemoval() 방법.
    // Callbacks to allow LinkedHashMap post-actions
    void afterNodeAccess(Node p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node p) { }
    //    ,          ,   evict                    。    LruCache       。
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry first;
        //LinkedHashMap     false       
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }
    //LinkedHashMap     false       。   true           。      LruCache    Cache      true
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return false;
    }
void afterNodeInsertion(boolean evict)boolean removeEldestEntry(Map.Entry eldest) 는 LruCache 를 구축 하 는 데 필요 한 반전 으로 LinkedHashMap 에서 무시 할 수 있다.
삭제LinkedHashMap 재 작성 remove() 방법 도 없다. 삭제 논리 와 HashMap 차이 가 없 기 때문이다.그러나 그것 은 이 반전 방법 을 다시 썼 다.이 방법 은 afterNodeRemoval() 방법 에서 리 셋 되 고 Node removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) 노드 를 삭제 하 는 모든 방법 에서 호출 된다. 앞에서 분석 한 바 와 같이 노드 작업 을 삭제 하 는 진정한 집행자 이다.
    //     e ,   e        
    void afterNodeRemoval(Node e) { // unlink
        LinkedHashMap.Entry p =
            (LinkedHashMap.Entry)e, b = p.before, a = p.after;
        //      p           
        p.before = p.after = null;
        //       null,              a
        if (b == null)
            head = a;
        else//       b       a
            b.after = a;
        //         null ,      b
        if (a == null)
            tail = b;
        else//        a      b
            a.before = b;
    }

조사 하 다removeNode() 다시 쓰기 LinkedHashMap 방법:
    public V get(Object key) {
        Node e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }
    public V getOrDefault(Object key, V defaultValue) {
       Node e;
       if ((e = getNode(hash(key), key)) == null)
           return defaultValue;
       if (accessOrder)
           afterNodeAccess(e);
       return e.value;
   }

대비 get() getOrDefault() 에서 의 실현 HashMap 은 구성원 변수 (구조 함수 시 할당) LinkedHashMap 가 true 인 상황 에서 반전 accessOrder 함 수 를 증가 시 켰 을 뿐이다.
    public V get(Object key) {
        Node e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
void afterNodeAccess(Node e) 함수 에서 현재 방문 한 노드 e 를 내부 의 양 방향 링크 의 끝으로 이동 합 니 다.
    void afterNodeAccess(Node e) { // move node to last
        LinkedHashMap.Entry last;//    
        //  accessOrder  true ,        e
        if (accessOrder && (last = tail) != e) {
            //  e         p
            LinkedHashMap.Entry p =
                (LinkedHashMap.Entry)e, b = p.before, a = p.after;
            //p      ,        null
            p.after = null;
            //  p      null, p      ,           p     a
            if (b == null)
                head = a;
            else//    p     b       a
                b.after = a;
            //  p       null,       a      b
            if (a != null)
                a.before = b;
            else//    p      null, p     。      last     p     b
                last = b;
            if (last == null) //      null   ,        
                head = p;
            else {//          p           last, last      p
                p.before = last;
                last.after = p;
            }
            //         p
            tail = p;
            //  modCount。
            ++modCount;
        }
    }

주의해 야 할 것 은 afterNodeAccess() 함수 에서 수정 afterNodeAccess() 하기 때문에 modCount 모드 에서 교체 accessOrder=true 할 때 방문 데 이 터 를 동시에 조회 하면 LinkedHashMap 교체 순서 가 바 뀌 었 기 때문이다.
7.2 containsValue
그것 은 이 방법 을 다시 써 서 fail-fast 의 실현 보다 더욱 효율 적 이다.
    public boolean containsValue(Object value) {
        //      ,      value     ,   
        for (LinkedHashMap.Entry e = head; e != null; e = e.after) {
            V v = e.value;
            if (v == value || (value != null && value.equals(v)))
                return true;
        }
        return false;
    }

대비 HashMap 는 두 개의 for 순환 으로 옮 겨 다 니 며 상대 적 으로 비효 율 적 입 니 다.
    public boolean containsValue(Object value) {
        Node[] tab; V v;
        if ((tab = table) != null && size > 0) {
            for (int i = 0; i < tab.length; ++i) {
                for (Node e = tab[i]; e != null; e = e.next) {
                    if ((v = e.value) == value ||
                        (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
    }

편력
다시 썼 습 니 다 HashMap 다음 과 같 습 니 다.
    public Set> entrySet() {
        Set> es;
        //  LinkedEntrySet
        return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
    }
    final class LinkedEntrySet extends AbstractSet> {
        public final Iterator> iterator() {
            return new LinkedEntryIterator();
        }
    }

최종 Entry Iterator:
    final class LinkedEntryIterator extends LinkedHashIterator
        implements Iterator> {
        public final Map.Entry next() { return nextNode(); }
    }
    
    abstract class LinkedHashIterator {
        //     
        LinkedHashMap.Entry next;
        //    
        LinkedHashMap.Entry current;
        int expectedModCount;

        LinkedHashIterator() {
            //    ,next   LinkedHashMap            
            next = head;
            //    modCount,   fail-fast
            expectedModCount = modCount;
            //     null
            current = null;
        }
        //      next
        public final boolean hasNext() {
            //    next   null,  next head    
            return next != null;
        }
        //nextNode()        next()   。
        //          ,  LinkedHashMap,                    。
        final LinkedHashMap.Entry nextNode() {
            //      e。
            LinkedHashMap.Entry e = next;
            //  fail-fast
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            //         null,  
            if (e == null)
                throw new NoSuchElementException();
            //       e
            current = e;
            //        e     
            next = e.after;
            //  e
            return e;
        }
        //            HashMap removeNode  
        public final void remove() {
            Node p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }
    

주의해 야 할 것 은 entrySet() 교체 기 안의 nextNode() 방법 이다.이 방법의 실현 을 통 해 알 수 있 듯 이 교체 next() 는 내부 에서 유지 하 는 더 블 링크 의 헤더 부터 순환 수출 하 는 것 이다.한편, 쌍 링크 노드 의 순 서 는 LinkedHashMap 의 증가, 삭제, 수정, 검사 할 때 모두 업데이트 된다.삽입 순서에 따라 출력 할 지, 접근 순서에 따라 출력 할 지 만족 합 니 다.
총결산LinkedHashMapLinkedHashMap 의 소스 코드 에 비해 간단 하 다.큰 나무 밑 은 더 위 를 잘 식 히 기 때문이다.그것 은 반복 되 는 순 서 를 바 꾸 기 위해 몇 가지 방법 만 다시 썼 다.HashMap 과 비교 해 가장 큰 차이 이기 도 하 다.데 이 터 를 삽입 하거나 데 이 터 를 방문 하거나 수정 할 때마다 노드 를 증가 하거나 링크 의 노드 순 서 를 조정 합 니 다.교체 시 출력 순 서 를 결정 합 니 다.
  • HashMap 기본 값 은 false 이 고 교체 할 때 출력 순 서 는 노드 를 삽입 하 는 순서 입 니 다.true 라면 출력 순 서 는 방문 노드 의 순서에 따른다.true 를 위 한 경우 이 기초 위 에 하 나 를 구축 할 수 있 습 니 다 HashMap.
  • accessOrder 는 put 방법 을 다시 쓰 지 않 았 다.그러나 이 는 새로운 노드 를 구축 하 는 LruCache 방법 을 다시 썼 다. 새로운 노드 를 구축 할 때마다 새로운 노드 를 내부 양 방향 링크 의 끝 부분
  • 에 연결 했다.
  • LinkedHashMap 의 모드 에서 newNode() 함수 에서 현재 방문 한 노드 e 를 내부 의 양 방향 링크 의 꼬리 부분 으로 이동 합 니 다.주의해 야 할 것 은 accessOrder=true 함수 에서 수정 afterNodeAccess() 하기 때문에 afterNodeAccess() 모드 에서 교체 modCount 할 때 방문 데 이 터 를 동시에 조회 하면 accessOrder=true 교체 순서 가 바 뀌 었 기 때문이다.
  • LinkedHashMap 는 교체 기 안의 fail-fast 방법 이다.이 방법의 실현 을 통 해 알 수 있 듯 이 교체 nextNode() 는 내부 에서 유지 하 는 더 블 링크 의 헤더 부터 순환 수출 하 는 것 이다.한편, 쌍 링크 노드 의 순 서 는 next() 의 증가, 삭제, 수정, 검사 할 때 모두 업데이트 된다.삽입 순서에 따라 출력 할 지, 접근 순서에 따라 출력 할 지 만족 합 니 다.
  • 이 는 LinkedHashMap 에 비해 작은 최적화 가 있 고 LinkedHashMap 방법 을 재 작성 하여 내부 링크 를 직접 옮 겨 다 니 며 value 값 이 같 는 지 비교 했다.

  • 그럼 마지막 작은 문제 가 있 나 요?왜 재 작성 HashMap 방법 도 내부 링크 의 key 보다 순환 하지 않 습 니까?

    좋은 웹페이지 즐겨찾기