1. 程式人生 > >6.3 HashMap 內部類 KeySet、Values、EntrySet

6.3 HashMap 內部類 KeySet、Values、EntrySet

KeySet、Values、EntrySet 操作其實都差不多,其內部並不儲存任何資料,這三個內部內都是封裝了方便外部對於 table 的 key,value,node 的遍歷和操作

一、KeySet

是 HashMap 中所有 key 的集合

// HashMap 的 table 中所有 key 的集合
final class KeySet extends AbstractSet<K> {
	// 獲取到的其實是 table 的元素個數,並不是 key 的個數,不過其實也一樣
    public final int size()                 { return size;
} // 呼叫的外部的 clear(),所以呼叫這個方法會清空 table 中的資料, // 裡面的原理其實不是 table 引用重指向,而是 table 中的桶賦空 public final void clear() { HashMap.this.clear(); } // 返回一個用於迭代 KeySet 的迭代器 public final Iterator<K> iterator() { return new KeyIterator(); } // 呼叫外部 containsKey(o) 方法,用於查詢 table 是否存在 key 為 o 的元素
public final boolean contains(Object o) { return containsKey(o); } // 根據 key 移除元素,同樣和對外暴露的 remove(Object key),功能一樣 public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } // 單看方法名的話就是一個分割迭代器了,同樣用於迭代,但是分割了,所以可以並行迭代,具體操作後面關於並行迭代器再說~~
public final Spliterator<K> spliterator() { return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } // jdk1.8 中出現的函數語言程式設計,裡面的引數是一個函式式介面, // 也就是說可以傳入該介面的實現,也可以傳入一個 Lambda 表示式(箭頭函式) public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } }

二、Values

是 HashMap 中所有 value 的集合

//HashMap 的 table 中所有 value 的集合
final class Values extends AbstractCollection<V> {
	// 獲取到的其實是 table 的元素個數,並不是 value 的個數,不過其實也一樣
    public final int size()                 { return size; }
    // 和 KeySet 的功能一樣
    public final void clear()               { HashMap.this.clear(); }
    // 獲取一個用於迭代 value 的迭代器
    public final Iterator<V> iterator()     { return new ValueIterator(); }
    // 和 table 對外暴露的 containsValue(Object value),方法一樣,判斷 table 中是否存在值為 o 的元素
    public final boolean contains(Object o) { return containsValue(o); }
    // 獲取一個 value 的分割迭代器,功能和操作類似 KeySet
    public final Spliterator<V> spliterator() {
        return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    // 功能和操作類似 KeySet
    public final void forEach(Consumer<? super V> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e.value);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

三、EntrySet

EntrySet 就不解釋了,原理都一樣,細節稍有不同,這三者較大的不同點在於 KeySet 和 EntrySet 繼承於 AbstractSet,Values 繼承於 AbstractCollection,這個就和裡面的值能否重複有關了,KeySet 和 EntrySet 不允許重複,Values 允許有相同
另外一個要注意的是利用 EntrySet 移除節點時,會對 table 的元素進行重排,慎用~