Java集合詳解7:一文搞清楚HashSet,TreeSet與LinkedHashSet的異同

本系列文章將整理到我在GitHub上的《Java面試指南》倉(cāng)庫(kù),更多精彩內(nèi)容請(qǐng)到我的倉(cāng)庫(kù)里查看

創(chuàng)新互聯(lián)建站是一家專(zhuān)業(yè)提供社旗企業(yè)網(wǎng)站建設(shè),專(zhuān)注與成都網(wǎng)站制作、網(wǎng)站建設(shè)、H5場(chǎng)景定制、小程序制作等業(yè)務(wù)。10年已為社旗眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專(zhuān)業(yè)網(wǎng)絡(luò)公司優(yōu)惠進(jìn)行中。

https://github.com/h3pl/Java-Tutorial

喜歡的話麻煩點(diǎn)下Star哈

文章首發(fā)于我的個(gè)人博客:

www.how2playlife.com

本文是微信公眾號(hào)【Java技術(shù)江湖】的《走進(jìn)JavaWeb技術(shù)世界》其中一篇,本文部分內(nèi)容來(lái)源于網(wǎng)絡(luò),為了把本文主題講得清晰透徹,也整合了很多我認(rèn)為不錯(cuò)的技術(shù)博客內(nèi)容,引用其中了一些比較好的博客文章,如有侵權(quán),請(qǐng)聯(lián)系作者。

該系列博文會(huì)告訴你如何從入門(mén)到進(jìn)階,從servlet到框架,從ssm再到SpringBoot,一步步地學(xué)習(xí)JavaWeb基礎(chǔ)知識(shí),并上手進(jìn)行實(shí)戰(zhàn),接著了解JavaWeb項(xiàng)目中經(jīng)常要使用的技術(shù)和組件,包括日志組件、Maven、Junit,等等內(nèi)容,以便讓你更完整地了解整個(gè)JavaWeb技術(shù)體系,形成自己的知識(shí)框架。為了更好地總結(jié)和檢驗(yàn)?zāi)愕膶W(xué)習(xí)成果,本系列文章也會(huì)提供每個(gè)知識(shí)點(diǎn)對(duì)應(yīng)的面試題以及參考答案。

如果對(duì)本系列文章有什么建議,或者是有什么疑問(wèn)的話,也可以關(guān)注公眾號(hào)【Java技術(shù)江湖】聯(lián)系作者,歡迎你參與本系列博文的創(chuàng)作和修訂。

文末贈(zèng)送8000G的Java架構(gòu)師學(xué)習(xí)資料,需要的朋友可以到文末了解領(lǐng)取方式,資料包括Java基礎(chǔ)、進(jìn)階、項(xiàng)目和架構(gòu)師等免費(fèi)學(xué)習(xí)資料,更有數(shù)據(jù)庫(kù)、分布式、微服務(wù)等熱門(mén)技術(shù)學(xué)習(xí)視頻,內(nèi)容豐富,兼顧原理和實(shí)踐,另外也將贈(zèng)送作者原創(chuàng)的Java學(xué)習(xí)指南、Java程序員面試指南等干貨資源)

今天我們來(lái)探索一下HashSet,TreeSet與LinkedHashSet的基本原理與源碼實(shí)現(xiàn),由于這三個(gè)set都是基于之前文章的三個(gè)map進(jìn)行實(shí)現(xiàn)的,所以推薦大家先看一下前面有關(guān)map的文章,結(jié)合使用味道更佳。

本文參考
http://cmsblogs.com/?p=599

HashSet

定義

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

HashSet繼承AbstractSet類(lèi),實(shí)現(xiàn)Set、Cloneable、Serializable接口。其中AbstractSet提供 Set 接口的骨干實(shí)現(xiàn),從而最大限度地減少了實(shí)現(xiàn)此接口所需的工作。
==Set接口是一種不包括重復(fù)元素的Collection,它維持它自己的內(nèi)部排序,所以隨機(jī)訪問(wèn)沒(méi)有任何意義。==

本文基于1.8jdk進(jìn)行源碼分析。

基本屬性

基于HashMap實(shí)現(xiàn),底層使用HashMap保存所有元素

private transient HashMap<E,Object> map;
//定義一個(gè)Object對(duì)象作為HashMap的value
private static final Object PRESENT = new Object();

構(gòu)造函數(shù)

/**
     * 默認(rèn)構(gòu)造函數(shù)
     * 初始化一個(gè)空的HashMap,并使用默認(rèn)初始容量為16和加載因子0.75。
     */
    public HashSet() {
        map = new HashMap<>();
    }
    /**
     * 構(gòu)造一個(gè)包含指定 collection 中的元素的新 set。
     */
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
    /**
     * 構(gòu)造一個(gè)新的空 set,其底層 HashMap 實(shí)例具有指定的初始容量和指定的加載因子
     */
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
    /**
     * 構(gòu)造一個(gè)新的空 set,其底層 HashMap 實(shí)例具有指定的初始容量和默認(rèn)的加載因子(0.75)。
     */
    public HashSet(int initialCapacity) {
       map = new HashMap<>(initialCapacity);
    }
    /**
     * 在API中我沒(méi)有看到這個(gè)構(gòu)造函數(shù),今天看源碼才發(fā)現(xiàn)(原來(lái)訪問(wèn)權(quán)限為包權(quán)限,不對(duì)外公開(kāi)的)
     * 以指定的initialCapacity和loadFactor構(gòu)造一個(gè)新的空鏈接哈希集合。
     * dummy 為標(biāo)識(shí) 該構(gòu)造函數(shù)主要作用是對(duì)LinkedHashSet起到一個(gè)支持作用
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
       map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
 從構(gòu)造函數(shù)中可以看出HashSet所有的構(gòu)造都是構(gòu)造出一個(gè)新的HashMap,其中最后一個(gè)構(gòu)造函數(shù),為包訪問(wèn)權(quán)限是不對(duì)外公開(kāi),僅僅只在使用LinkedHashSet時(shí)才會(huì)發(fā)生作用。

方法

既然HashSet是基于HashMap,那么對(duì)于HashSet而言,其方法的實(shí)現(xiàn)過(guò)程是非常簡(jiǎn)單的。

public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

iterator()方法返回對(duì)此 set 中元素進(jìn)行迭代的迭代器。返回元素的順序并不是特定的。

底層調(diào)用HashMap的keySet返回所有的key,這點(diǎn)反應(yīng)了HashSet中的所有元素都是保存在HashMap的key中,value則是使用的PRESENT對(duì)象,該對(duì)象為static final。

public int size() {
        return map.size();
    }
   size()返回此 set 中的元素的數(shù)量(set 的容量)。底層調(diào)用HashMap的size方法,返回HashMap容器的大小。
public boolean isEmpty() {
        return map.isEmpty();
    }
    isEmpty(),判斷HashSet()集合是否為空,為空返回 true,否則返回false。
public boolean contains(Object o) {
        return map.containsKey(o);
}
public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}
//最終調(diào)用該方法進(jìn)行節(jié)點(diǎn)查找
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    //先檢查桶的頭結(jié)點(diǎn)是否存在
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
            //不是頭結(jié)點(diǎn),則遍歷鏈表,如果是樹(shù)節(jié)點(diǎn)則使用樹(shù)節(jié)點(diǎn)的方法遍歷,直到找到,或者為null
        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;
}

contains(),判斷某個(gè)元素是否存在于HashSet()中,存在返回true,否則返回false。更加確切的講應(yīng)該是要滿(mǎn)足這種關(guān)系才能返回true:(o==null ? e==null : o.equals(e))。底層調(diào)用containsKey判斷HashMap的key值是否為空。

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
map的put方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //確認(rèn)初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //如果桶為空,直接插入新元素,也就是entry
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        //如果沖突,分為三種情況
        //key相等時(shí)讓舊entry等于新entry即可
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //紅黑樹(shù)情況
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            //如果key不相等,則連成鏈表
            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;
}

這里注意一點(diǎn),hashset只是不允許重復(fù)的元素加入,而不是不允許元素連成鏈表,因?yàn)橹灰猭ey的equals方法判斷為true時(shí)它們是相等的,此時(shí)會(huì)發(fā)生value的替換,因?yàn)樗衑ntry的value一樣,所以和沒(méi)有插入時(shí)一樣的。

而當(dāng)兩個(gè)hashcode相同但key不相等的entry插入時(shí),仍然會(huì)連成一個(gè)鏈表,長(zhǎng)度超過(guò)8時(shí)依然會(huì)和hashmap一樣擴(kuò)展成紅黑樹(shù),看完源碼之后筆者才明白自己之前理解錯(cuò)了。所以看源碼還是蠻有好處的。hashset基本上就是使用hashmap的方法再次實(shí)現(xiàn)了一遍而已,只不過(guò)value全都是同一個(gè)object,讓你以為相同元素沒(méi)有插入,事實(shí)上只是value替換成和原來(lái)相同的值而已。

當(dāng)add方法發(fā)生沖突時(shí),如果key相同,則替換value,如果key不同,則連成鏈表。

add()如果此 set 中尚未包含指定元素,則添加指定元素。如果此Set沒(méi)有包含滿(mǎn)足(e==null ? e2==null : e.equals(e2)) 的e2時(shí),則將e2添加到Set中,否則不添加且返回false。

由于底層使用HashMap的put方法將key = e,value=PRESENT構(gòu)建成key-value鍵值對(duì),當(dāng)此e存在于HashMap的key中,則value將會(huì)覆蓋原有value,但是key保持不變,所以如果將一個(gè)已經(jīng)存在的e元素添加中HashSet中,新添加的元素是不會(huì)保存到HashMap中,所以這就滿(mǎn)足了HashSet中元素不會(huì)重復(fù)的特性。

public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

remove如果指定元素存在于此 set 中,則將其移除。底層使用HashMap的remove方法刪除指定的Entry。

public void clear() {
    map.clear();
}

clear從此 set 中移除所有元素。底層調(diào)用HashMap的clear方法清除所有的Entry。

public Object clone() {
        try {
            HashSet<E> newSet = (HashSet<E>) super.clone();
            newSet.map = (HashMap<E, Object>) map.clone();
            return newSet;
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
    }

clone返回此 HashSet 實(shí)例的淺表副本:并沒(méi)有復(fù)制這些元素本身。

后記:

由于HashSet底層使用了HashMap實(shí)現(xiàn),使其的實(shí)現(xiàn)過(guò)程變得非常簡(jiǎn)單,如果你對(duì)HashMap比較了解,那么HashSet簡(jiǎn)直是小菜一碟。有兩個(gè)方法對(duì)HashMap和HashSet而言是非常重要的,下篇將詳細(xì)講解hashcode和equals。

TreeSet

與HashSet是基于HashMap實(shí)現(xiàn)一樣,TreeSet同樣是基于TreeMap實(shí)現(xiàn)的。在《Java提高篇(二七)——-TreeMap》中LZ詳細(xì)講解了TreeMap實(shí)現(xiàn)機(jī)制,如果客官詳情看了這篇博文或者多TreeMap有比較詳細(xì)的了解,那么TreeSet的實(shí)現(xiàn)對(duì)您是喝口水那么簡(jiǎn)單。

TreeSet定義

我們知道TreeMap是一個(gè)有序的二叉樹(shù),那么同理TreeSet同樣也是一個(gè)有序的,它的作用是提供有序的Set集合。通過(guò)源碼我們知道TreeSet基礎(chǔ)AbstractSet,實(shí)現(xiàn)NavigableSet、Cloneable、Serializable接口。

其中AbstractSet提供 Set 接口的骨干實(shí)現(xiàn),從而最大限度地減少了實(shí)現(xiàn)此接口所需的工作。

NavigableSet是擴(kuò)展的 SortedSet,具有了為給定搜索目標(biāo)報(bào)告最接近匹配項(xiàng)的導(dǎo)航方法,這就意味著它支持一系列的導(dǎo)航方法。比如查找與指定目標(biāo)最匹配項(xiàng)。Cloneable支持克隆,Serializable支持序列化。

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable

同時(shí)在TreeSet中定義了如下幾個(gè)變量。

private transient NavigableMap<E,Object> m;
//PRESENT會(huì)被當(dāng)做Map的value與key構(gòu)建成鍵值對(duì)
 private static final Object PRESENT = new Object();

其構(gòu)造方法:

//默認(rèn)構(gòu)造方法,根據(jù)其元素的自然順序進(jìn)行排序
public TreeSet() {
    this(new TreeMap<E,Object>());
}
//構(gòu)造一個(gè)包含指定 collection 元素的新 TreeSet,它按照其元素的自然順序進(jìn)行排序。
public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
}
//構(gòu)造一個(gè)新的空 TreeSet,它根據(jù)指定比較器進(jìn)行排序。
public TreeSet(Collection<? extends E> c) {
    this();
    addAll(c);
}
//構(gòu)造一個(gè)與指定有序 set 具有相同映射關(guān)系和相同排序的新 TreeSet。
public TreeSet(SortedSet<E> s) {
    this(s.comparator());
    addAll(s);
}
TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
}

TreeSet主要方法

1、add:將指定的元素添加到此 set(如果該元素尚未存在于 set 中)。

public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
    //空樹(shù)時(shí),判斷節(jié)點(diǎn)是否為空
        compare(key, key); // type (and possibly null) check
        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    //非空樹(shù),根據(jù)傳入比較器進(jìn)行節(jié)點(diǎn)的插入位置查找
    if (cpr != null) {
        do {
            parent = t;
            //節(jié)點(diǎn)比根節(jié)點(diǎn)小,則找左子樹(shù),否則找右子樹(shù)
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
                //如果key的比較返回值相等,直接更新值(一般compareto相等時(shí)equals方法也相等)
            else
                return t.setValue(value);
        } while (t != null);
    }
    else {
    //如果沒(méi)有傳入比較器,則按照自然排序
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    //查找的節(jié)點(diǎn)為空,直接插入,默認(rèn)為紅節(jié)點(diǎn)
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
        //插入后進(jìn)行紅黑樹(shù)調(diào)整
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}    

2、get:獲取元素

public V get(Object key) {
    Entry<K,V> p = getEntry(key);
    return (p==null ? null : p.value);
}

該方法與put的流程類(lèi)似,只不過(guò)是把插入換成了查找

3、ceiling:返回此 set 中大于等于給定元素的最小元素;如果不存在這樣的元素,則返回 null。

public E ceiling(E e) {
        return m.ceilingKey(e);
    }

4、clear:移除此 set 中的所有元素。

public void clear() {
        m.clear();
    }

5、clone:返回 TreeSet 實(shí)例的淺表副本。屬于淺拷貝。

public Object clone() {
        TreeSet<E> clone = null;
        try {
            clone = (TreeSet<E>) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
        clone.m = new TreeMap<>(m);
        return clone;
    }

6、comparator:返回對(duì)此 set 中的元素進(jìn)行排序的比較器;如果此 set 使用其元素的自然順序,則返回 null。

public Comparator<? super E> comparator() {
        return m.comparator();
    }

7、contains:如果此 set 包含指定的元素,則返回 true。

public boolean contains(Object o) {
        return m.containsKey(o);
    }

8、descendingIterator:返回在此 set 元素上按降序進(jìn)行迭代的迭代器。

public Iterator<E> descendingIterator() {
        return m.descendingKeySet().iterator();
    }

9、descendingSet:返回此 set 中所包含元素的逆序視圖。

public NavigableSet<E> descendingSet() {
        return new TreeSet<>(m.descendingMap());
    }

10、first:返回此 set 中當(dāng)前第一個(gè)(最低)元素。

public E first() {
        return m.firstKey();
    }

11、floor:返回此 set 中小于等于給定元素的最大元素;如果不存在這樣的元素,則返回 null。

public E floor(E e) {
        return m.floorKey(e);
    }

12、headSet:返回此 set 的部分視圖,其元素嚴(yán)格小于 toElement。

public SortedSet<E> headSet(E toElement) {
        return headSet(toElement, false);
    }

13、higher:返回此 set 中嚴(yán)格大于給定元素的最小元素;如果不存在這樣的元素,則返回 null。

public E higher(E e) {
        return m.higherKey(e);
    }

14、isEmpty:如果此 set 不包含任何元素,則返回 true。

public boolean isEmpty() {
        return m.isEmpty();
    }

15、iterator:返回在此 set 中的元素上按升序進(jìn)行迭代的迭代器。

public Iterator<E> iterator() {
        return m.navigableKeySet().iterator();
    }

16、last:返回此 set 中當(dāng)前最后一個(gè)(最高)元素。

public E last() {
        return m.lastKey();
    }

17、lower:返回此 set 中嚴(yán)格小于給定元素的最大元素;如果不存在這樣的元素,則返回 null。

public E lower(E e) {
        return m.lowerKey(e);
    }

18、pollFirst:獲取并移除第一個(gè)(最低)元素;如果此 set 為空,則返回 null。

public E pollFirst() {
        Map.Entry<E,?> e = m.pollFirstEntry();
        return (e == null) ? null : e.getKey();
    }

19、pollLast:獲取并移除最后一個(gè)(最高)元素;如果此 set 為空,則返回 null。

public E pollLast() {
        Map.Entry<E,?> e = m.pollLastEntry();
        return (e == null) ? null : e.getKey();
    }

20、remove:將指定的元素從 set 中移除(如果該元素存在于此 set 中)。

public boolean remove(Object o) {
        return m.remove(o)==PRESENT;
    }

該方法與put類(lèi)似,只不過(guò)把插入換成了刪除,并且要進(jìn)行刪除后調(diào)整

21、size:返回 set 中的元素?cái)?shù)(set 的容量)。

public int size() {
        return m.size();
    }

22、subSet:返回此 set 的部分視圖

/**
     * 返回此 set 的部分視圖,其元素范圍從 fromElement 到 toElement。
     */
     public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
             E toElement,   boolean toInclusive) {
             return new TreeSet<>(m.subMap(fromElement, fromInclusive,
                  toElement,   toInclusive));
     }
     /**
      * 返回此 set 的部分視圖,其元素從 fromElement(包括)到 toElement(不包括)。
      */
     public SortedSet<E> subSet(E fromElement, E toElement) {
         return subSet(fromElement, true, toElement, false);
     }

23、tailSet:返回此 set 的部分視圖

/**
     * 返回此 set 的部分視圖,其元素大于(或等于,如果 inclusive 為 true)fromElement。
     */
    public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
        return new TreeSet<>(m.tailMap(fromElement, inclusive));
    }
    /**
     * 返回此 set 的部分視圖,其元素大于等于 fromElement。
     */
    public SortedSet<E> tailSet(E fromElement) {
        return tailSet(fromElement, true);
    }

最后

由于TreeSet是基于TreeMap實(shí)現(xiàn)的,所以如果我們對(duì)treeMap有了一定的了解,對(duì)TreeSet那是小菜一碟,我們從TreeSet中的源碼可以看出,其實(shí)現(xiàn)過(guò)程非常簡(jiǎn)單,幾乎所有的方法實(shí)現(xiàn)全部都是基于TreeMap的。

LinkedHashSet

LinkedHashSet內(nèi)部是如何工作的

LinkedHashSet是HashSet的一個(gè)“擴(kuò)展版本”,HashSet并不管什么順序,不同的是LinkedHashSet會(huì)維護(hù)“插入順序”。HashSet內(nèi)部使用HashMap對(duì)象來(lái)存儲(chǔ)它的元素,而LinkedHashSet內(nèi)部使用LinkedHashMap對(duì)象來(lái)存儲(chǔ)和處理它的元素。這篇文章,我們將會(huì)看到LinkedHashSet內(nèi)部是如何運(yùn)作的及如何維護(hù)插入順序的。

我們首先著眼LinkedHashSet的構(gòu)造函數(shù)。在LinkedHashSet類(lèi)中一共有4個(gè)構(gòu)造函數(shù)。這些構(gòu)造函數(shù)都只是簡(jiǎn)單地調(diào)用父類(lèi)構(gòu)造函數(shù)(如HashSet類(lèi)的構(gòu)造函數(shù))。
下面看看LinkedHashSet的構(gòu)造函數(shù)是如何定義的。

//Constructor - 1
public LinkedHashSet(int initialCapacity, float loadFactor)
{
      super(initialCapacity, loadFactor, true);              //Calling super class constructor
}
//Constructor - 2
public LinkedHashSet(int initialCapacity)
{
        super(initialCapacity, .75f, true);             //Calling super class constructor
}
//Constructor - 3
public LinkedHashSet()
{
        super(16, .75f, true);                //Calling super class constructor
}
//Constructor - 4
public LinkedHashSet(Collection<? extends E> c)
{
        super(Math.max(2*c.size(), 11), .75f, true);          //Calling super class constructor
        addAll(c);
}

在上面的代碼片段中,你可能注意到4個(gè)構(gòu)造函數(shù)調(diào)用的是同一個(gè)父類(lèi)的構(gòu)造函數(shù)。這個(gè)構(gòu)造函數(shù)(父類(lèi)的,譯者注)是一個(gè)包內(nèi)私有構(gòu)造函數(shù)(見(jiàn)下面的代碼,HashSet的構(gòu)造函數(shù)沒(méi)有使用public公開(kāi),譯者注),它只能被LinkedHashSet使用。

這個(gè)構(gòu)造函數(shù)需要初始容量,負(fù)載因子和一個(gè)boolean類(lèi)型的啞值(沒(méi)有什么用處的參數(shù),作為標(biāo)記,譯者注)等參數(shù)。這個(gè)啞參數(shù)只是用來(lái)區(qū)別這個(gè)構(gòu)造函數(shù)與HashSet的其他擁有初始容量和負(fù)載因子參數(shù)的構(gòu)造函數(shù),下面是這個(gè)構(gòu)造函數(shù)的定義,

HashSet(int initialCapacity, float loadFactor, boolean dummy)
{
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

顯然,這個(gè)構(gòu)造函數(shù)內(nèi)部初始化了一個(gè)LinkedHashMap對(duì)象,這個(gè)對(duì)象恰好被LinkedHashSet用來(lái)存儲(chǔ)它的元素。

LinkedHashSet并沒(méi)有自己的方法,所有的方法都繼承自它的父類(lèi)HashSet,因此,對(duì)LinkedHashSet的所有操作方式就好像對(duì)HashSet操作一樣。

唯一的不同是內(nèi)部使用不同的對(duì)象去存儲(chǔ)元素。在HashSet中,插入的元素是被當(dāng)做HashMap的鍵來(lái)保存的,而在LinkedHashSet中被看作是LinkedHashMap的鍵。

這些鍵對(duì)應(yīng)的值都是常量PRESENT(PRESENT是HashSet的靜態(tài)成員變量,譯者注)。

LinkedHashSet是如何維護(hù)插入順序的

LinkedHashSet使用LinkedHashMap對(duì)象來(lái)存儲(chǔ)它的元素,插入到LinkedHashSet中的元素實(shí)際上是被當(dāng)作LinkedHashMap的鍵保存起來(lái)的。

LinkedHashMap的每一個(gè)鍵值對(duì)都是通過(guò)內(nèi)部的靜態(tài)類(lèi)Entry 實(shí)例化的。這個(gè) Entry 類(lèi)繼承了HashMap.Entry類(lèi)。

這個(gè)靜態(tài)類(lèi)增加了兩個(gè)成員變量,before和after來(lái)維護(hù)LinkedHasMap元素的插入順序。這兩個(gè)成員變量分別指向前一個(gè)和后一個(gè)元素,這讓LinkedHashMap也有類(lèi)似雙向鏈表的表現(xiàn)。

private static class Entry<K,V> extends HashMap.Entry<K,V>
{
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }
}

從上面代碼看到的LinkedHashMap內(nèi)部類(lèi)的前面兩個(gè)成員變量——before和after負(fù)責(zé)維護(hù)LinkedHashSet的插入順序。LinkedHashMap定義的成員變量header保存的是
這個(gè)雙向鏈表的頭節(jié)點(diǎn)。header的定義就像下面這樣,

接下來(lái)看一個(gè)例子就知道LinkedHashSet內(nèi)部是如何工作的了。

public class LinkedHashSetExample
{
    public static void main(String[] args)
    {
        //Creating LinkedHashSet
        LinkedHashSet<String> set = new LinkedHashSet<String>();
        //Adding elements to LinkedHashSet
        set.add("BLUE");
        set.add("RED");
        set.add("GREEN");    
        set.add("BLACK");
    }
}

如果你知道LinkedHashMap內(nèi)部是如何工作的,就非常容易明白LinkedHashSet內(nèi)部是如何工作的??匆槐長(zhǎng)inkedHashSet和LinkedHashMap的源碼,
你就能夠準(zhǔn)確地理解在Java中LinkedHashSet內(nèi)部是如何工作的。

參考文章

http://cmsblogs.com/?p=599

https://www.cnblogs.com/one-apple-pie/p/11036309.html

https://blog.csdn.net/learningcoding/article/details/79983248

網(wǎng)站欄目:Java集合詳解7:一文搞清楚HashSet,TreeSet與LinkedHashSet的異同
轉(zhuǎn)載注明:http://bm7419.com/article32/ipocsc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動(dòng)網(wǎng)站建設(shè)、搜索引擎優(yōu)化、品牌網(wǎng)站建設(shè)軟件開(kāi)發(fā)、云服務(wù)器、網(wǎng)站內(nèi)鏈

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都seo排名網(wǎng)站優(yōu)化