[搞懂Java集合類6]走近HashSet,TreeSet與LinkedHashSet

2022-01-08 Java技術江湖

今天我們來探索一下HashSet,TreeSet與LinkedHashSet的基本原理與源碼實現,由於這三個set都是基於之前文章的三個map進行實現的,所以推薦大家先看一下前面有關map的文章,結合使用味道更佳。

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

HashSet定義

public class HashSet<E>

   extends AbstractSet<E>

   implements Set<E>, Cloneable, java.io.Serializable

HashSet繼承AbstractSet類,實現Set、Cloneable、Serializable接口。其中AbstractSet提供 Set 接口的骨幹實現,從而最大限度地減少了實現此接口所需的工作。==Set接口是一種不包括重複元素的Collection,它維持它自己的內部排序,所以隨機訪問沒有任何意義。==

本文基於1.8jdk進行源碼分析。

基本屬性

基於HashMap實現,底層使用HashMap保存所有元素

private transient HashMap<E,Object> map;

//定義一個Object對象作為HashMap的value

private static final Object PRESENT = new Object();

構造函數

/**

    * 默認構造函數

    * 初始化一個空的HashMap,並使用默認初始容量為16和加載因子0.75。

    */

   public HashSet() {

       map = new HashMap<>();

   }

   /**

    * 構造一個包含指定 collection 中的元素的新 set。

    */

   public HashSet(Collection<? extends E> c) {

       map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));

       addAll(c);

   }

   /**

    * 構造一個新的空 set,其底層 HashMap 實例具有指定的初始容量和指定的加載因子

    */

   public HashSet(int initialCapacity, float loadFactor) {

       map = new HashMap<>(initialCapacity, loadFactor);

   }

   /**

    * 構造一個新的空 set,其底層 HashMap 實例具有指定的初始容量和默認的加載因子(0.75)。

    */

   public HashSet(int initialCapacity) {

      map = new HashMap<>(initialCapacity);

   }

   /**

    * 在API中我沒有看到這個構造函數,今天看源碼才發現(原來訪問權限為包權限,不對外公開的)

    * 以指定的initialCapacity和loadFactor構造一個新的空連結哈希集合。

    * dummy 為標識 該構造函數主要作用是對LinkedHashSet起到一個支持作用

    */

   HashSet(int initialCapacity, float loadFactor, boolean dummy) {

      map = new LinkedHashMap<>(initialCapacity, loadFactor);

   }

從構造函數中可以看出HashSet所有的構造都是構造出一個新的HashMap,其中最後一個構造函數,為包訪問權限是不對外公開,僅僅只在使用LinkedHashSet時才會發生作用。

方法

既然HashSet是基於HashMap,那麼對於HashSet而言,其方法的實現過程是非常簡單的。

public Iterator<E> iterator() {

       return map.keySet().iterator();

   }

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

底層調用HashMap的keySet返回所有的key,這點反應了HashSet中的所有元素都是保存在HashMap的key中,value則是使用的PRESENT對象,該對象為static final。

public int size() {

       return map.size();

   }

  size()返回此 set 中的元素的數量(set 的容量)。底層調用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;

}

//最終調用該方法進行節點查找

final Node<K,V> getNode(int hash, Object key) {

   Node<K,V>[] tab; Node<K,V> first, e; int n; K k;

   //先檢查桶的頭結點是否存在

   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;

           //不是頭結點,則遍歷鍊表,如果是樹節點則使用樹節點的方法遍歷,直到找到,或者為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(),判斷某個元素是否存在於HashSet()中,存在返回true,否則返回false。更加確切的講應該是要滿足這種關係才能返回true:(o==null ? e==null : o.equals(e))。底層調用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;

   //確認初始化

   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相等時讓舊entry等於新entry即可

       if (p.hash == hash &&

           ((k = p.key) == key || (key != null && key.equals(k))))

           e = p;

       //紅黑樹情況

       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;

}

這裡注意一點,hashset只是不允許重複的元素加入,而不是不允許元素連成鍊表,因為只要key的equals方法判斷為true時它們是相等的,此時會發生value的替換,因為所有entry的value一樣,所以和沒有插入時一樣的。

而當兩個hashcode相同但key不相等的entry插入時,仍然會連成一個鍊表,長度超過8時依然會和hashmap一樣擴展成紅黑樹,看完源碼之後筆者才明白自己之前理解錯了。所以看源碼還是蠻有好處的。hashset基本上就是使用hashmap的方法再次實現了一遍而已,只不過value全都是同一個object,讓你以為相同元素沒有插入,事實上只是value替換成和原來相同的值而已。

當add方法發生衝突時,如果key相同,則替換value,如果key不同,則連成鍊表。

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

由於底層使用HashMap的put方法將key = e,value=PRESENT構建成key-value鍵值對,當此e存在於HashMap的key中,則value將會覆蓋原有value,但是key保持不變,所以如果將一個已經存在的e元素添加中HashSet中,新添加的元素是不會保存到HashMap中,所以這就滿足了HashSet中元素不會重複的特性。

public boolean remove(Object o) {

   return map.remove(o)==PRESENT;

}

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

public void clear() {

   map.clear();

}

clear從此 set 中移除所有元素。底層調用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 實例的淺表副本:並沒有複製這些元素本身。

後記:

由於HashSet底層使用了HashMap實現,使其的實現過程變得非常簡單,如果你對HashMap比較了解,那麼HashSet簡直是小菜一碟。有兩個方法對HashMap和HashSet而言是非常重要的,下篇將詳細講解hashcode和equals。

TreeSet

與HashSet是基於HashMap實現一樣,TreeSet同樣是基於TreeMap實現的。在《Java提高篇(二七)TreeMap》中LZ詳細講解了TreeMap實現機制,如果客官詳情看了這篇博文或者多TreeMap有比較詳細的了解,那麼TreeSet的實現對您是喝口水那麼簡單。

TreeSet定義

我們知道TreeMap是一個有序的二叉樹,那麼同理TreeSet同樣也是一個有序的,它的作用是提供有序的Set集合。通過源碼我們知道TreeSet基礎AbstractSet,實現NavigableSet、Cloneable、Serializable接口。

其中AbstractSet提供 Set 接口的骨幹實現,從而最大限度地減少了實現此接口所需的工作。

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

public class TreeSet<E> extends AbstractSet<E>

   implements NavigableSet<E>, Cloneable, java.io.Serializable

同時在TreeSet中定義了如下幾個變量。

private transient NavigableMap<E,Object> m;

//PRESENT會被當做Map的value與key構建成鍵值對

private static final Object PRESENT = new Object();

其構造方法:

//默認構造方法,根據其元素的自然順序進行排序

public TreeSet() {

   this(new TreeMap<E,Object>());

}

//構造一個包含指定 collection 元素的新 TreeSet,它按照其元素的自然順序進行排序。

public TreeSet(Comparator<? super E> comparator) {

       this(new TreeMap<>(comparator));

}

//構造一個新的空 TreeSet,它根據指定比較器進行排序。

public TreeSet(Collection<? extends E> c) {

   this();

   addAll(c);

}

//構造一個與指定有序 set 具有相同映射關係和相同排序的新 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) {

   //空樹時,判斷節點是否為空

       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;

   //非空樹,根據傳入比較器進行節點的插入位置查找

   if (cpr != null) {

       do {

           parent = t;

           //節點比根節點小,則找左子樹,否則找右子樹

           cmp = cpr.compare(key, t.key);

           if (cmp < 0)

               t = t.left;

           else if (cmp > 0)

               t = t.right;

               //如果key的比較返回值相等,直接更新值(一般compareto相等時equals方法也相等)

           else

               return t.setValue(value);

       } while (t != null);

   }

   else {

   //如果沒有傳入比較器,則按照自然排序

       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);

   }

   //查找的節點為空,直接插入,默認為紅節點

   Entry<K,V> e = new Entry<>(key, value, parent);

   if (cmp < 0)

       parent.left = e;

   else

       parent.right = e;

       //插入後進行紅黑樹調整

   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的流程類似,只不過是把插入換成了查找

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

public E ceiling(E e) {

       return m.ceilingKey(e);

   }

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

public void clear() {

       m.clear();

   }

5、clone:返回 TreeSet 實例的淺表副本。屬於淺拷貝。

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:返回對此 set 中的元素進行排序的比較器;如果此 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 元素上按降序進行迭代的迭代器。

public Iterator<E> descendingIterator() {

       return m.descendingKeySet().iterator();

   }

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

public NavigableSet<E> descendingSet() {

       return new TreeSet<>(m.descendingMap());

   }

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

public E first() {

       return m.firstKey();

   }

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

public E floor(E e) {

       return m.floorKey(e);

   }

12、headSet:返回此 set 的部分視圖,其元素嚴格小於 toElement。

public SortedSet<E> headSet(E toElement) {

       return headSet(toElement, false);

   }

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

public E higher(E e) {

       return m.higherKey(e);

   }

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

public boolean isEmpty() {

       return m.isEmpty();

   }

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

public Iterator<E> iterator() {

       return m.navigableKeySet().iterator();

   }

16、last:返回此 set 中當前最後一個(最高)元素。

public E last() {

       return m.lastKey();

   }

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

public E lower(E e) {

       return m.lowerKey(e);

   }

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

public E pollFirst() {

       Map.Entry<E,?> e = m.pollFirstEntry();

       return (e == null) ? null : e.getKey();

   }

19、pollLast:獲取並移除最後一個(最高)元素;如果此 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類似,只不過把插入換成了刪除,並且要進行刪除後調整

21、size:返回 set 中的元素數(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實現的,所以如果我們對treeMap有了一定的了解,對TreeSet那是小菜一碟,我們從TreeSet中的源碼可以看出,其實現過程非常簡單,幾乎所有的方法實現全部都是基於TreeMap的。

LinkedHashSetLinkedHashSet內部是如何工作的

LinkedHashSet是HashSet的一個「擴展版本」,HashSet並不管什麼順序,不同的是LinkedHashSet會維護「插入順序」。HashSet內部使用HashMap對象來存儲它的元素,而LinkedHashSet內部使用LinkedHashMap對象來存儲和處理它的元素。這篇文章,我們將會看到LinkedHashSet內部是如何運作的及如何維護插入順序的。

我們首先著眼LinkedHashSet的構造函數。在LinkedHashSet類中一共有4個構造函數。這些構造函數都只是簡單地調用父類構造函數(如HashSet類的構造函數)。下面看看LinkedHashSet的構造函數是如何定義的。

//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個構造函數調用的是同一個父類的構造函數。這個構造函數(父類的,譯者注)是一個包內私有構造函數(見下面的代碼,HashSet的構造函數沒有使用public公開,譯者注),它只能被LinkedHashSet使用。

這個構造函數需要初始容量,負載因子和一個boolean類型的啞值(沒有什麼用處的參數,作為標記,譯者注)等參數。這個啞參數只是用來區別這個構造函數與HashSet的其他擁有初始容量和負載因子參數的構造函數,下面是這個構造函數的定義,

HashSet(int initialCapacity, float loadFactor, boolean dummy)

{

       map = new LinkedHashMap<>(initialCapacity, loadFactor);

}

顯然,這個構造函數內部初始化了一個LinkedHashMap對象,這個對象恰好被LinkedHashSet用來存儲它的元素。

LinkedHashSet並沒有自己的方法,所有的方法都繼承自它的父類HashSet,因此,對LinkedHashSet的所有操作方式就好像對HashSet操作一樣。

唯一的不同是內部使用不同的對象去存儲元素。在HashSet中,插入的元素是被當做HashMap的鍵來保存的,而在LinkedHashSet中被看作是LinkedHashMap的鍵。

這些鍵對應的值都是常量PRESENT(PRESENT是HashSet的靜態成員變量,譯者注)。

LinkedHashSet是如何維護插入順序的

LinkedHashSet使用LinkedHashMap對象來存儲它的元素,插入到LinkedHashSet中的元素實際上是被當作LinkedHashMap的鍵保存起來的。

LinkedHashMap的每一個鍵值對都是通過內部的靜態類Entry實例化的。這個 Entry類繼承了HashMap.Entry類。

這個靜態類增加了兩個成員變量,before和after來維護LinkedHasMap元素的插入順序。這兩個成員變量分別指向前一個和後一個元素,這讓LinkedHashMap也有類似雙向鍊表的表現。

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內部類的前面兩個成員變量——before和after負責維護LinkedHashSet的插入順序。LinkedHashMap定義的成員變量header保存的是這個雙向鍊表的頭節點。header的定義就像下面這樣,

接下來看一個例子就知道LinkedHashSet內部是如何工作的了。

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內部是如何工作的,就非常容易明白LinkedHashSet內部是如何工作的。看一遍LinkedHashSet和LinkedHashMap的源碼,你就能夠準確地理解在Java中LinkedHashSet內部是如何工作的。

相關焦點

  • java數組刪除重複元素專題及常見問題 - CSDN
    package com.akfucc.zhidao;import java.util.ArrayList;import java.util.Collections;import java.util.Iterator;import java.util.List
  • 如何使用 C# 中的 HashSet
    譯文連結:https://www.infoworld.com/article/3586972/how-to-use-hashset-in-csharp.htmlHashSet 是一個優化過的無序集合,提供對元素的高速查找和高性能的set集合操作,而且 HashSet 是在 .NET 3.5 中被引入的,在 System.Collection.Generic
  • 考考基礎部分,談談Java集合中HashSet的原理及常用方法
    HashSet概述 HashSet是Java集合Set的一個實現類,Set是一個接口,其實現類除HashSet之外,還有TreeSet,並繼承了Collection,HashSet集合很常用,同時也是程式設計師面試時經常會被問到的知識點,下面是結構圖
  • 面試題:java中HashSet詳解
    HashSet 底層採用 HashMap 來保存所有元素,因此 HashSet 的實現比較簡單,查看 HashSet 的原始碼,可以看到如下代碼:public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable
  • Java Set集合的詳解
    如果對兩個引用調用hashCode方法,會得到相同的結果,如果對象所屬的類沒有覆蓋Object的hashCode方法的話,hashCode會返回每個對象特有的序號(java是依據對象的內存地址計算出的此序號),所以兩個不同的對象的hashCode值是不可能相等的。
  • 說一下 HashSet 的實現原理?
    所以判斷key是否存在就要重寫元素的類的equals()和hashCode()方法,當向Set中添加對象時,首先調用此對象所在類的hashCode()方法,計算次對象的哈希值,此哈希值決定了此對象在Set中存放的位置;若此位置沒有被存儲對象則直接存儲,若已有對象則通過對象所在類的equals()比較兩個對象是否相同,相同則不能被添加。
  • java 基礎 之 集合 Map
    (linkedMap);    for(int i = 0; i < 6; i++)      linkedMap.get(i);    System.out.println(linkedMap);    linkedMap.get(0);    System.out.println(linkedMap);  }}/* Output
  • Java之LinkedList集合的簡單介紹
    各位小夥伴們大家好,在之前的文章中,小編介紹了Java之集合的簡單介紹,這次小編要介紹的是LinkedList集合,它是List接口的實現類。ArrayList集合也是List的一個實現類,它的底層是一個數組結構,這個結構的特點是查詢快,增刪慢。
  • Java中HashSet集合是如何對自定義對象進行去重
    Java中Set接口是Collectio的子接口,Set集合不允許包含相同的元素。如果添加相同的元素, add()會返回FALSE, 新元素不會加入。Set集合常用於元素為數字、字符串去重等,但是當元素為自定義對象類型時,Set去重是否與我們預計一致?下面將以HashSet為例,通過一系列試驗來一步步驗證。
  • 每日一課 | Java 中如何將 ArrayList 與 HashSet 互相轉換?
    將列錶轉換為集合Set set = new HashSet(list);將集轉換為列表List list = new ArrayList(set);1.列出示例import java.util.ArrayList;
  • 跟我學Java編程—掌握LinkedHashSet的用法
    前面介紹了Set接口的實現類HashSet,本節介紹Set接口的另一個實現類LinkedHashSet。相對HashSet來說,LinkedHashSet存儲結構是一個雙向鍊表,因此它存儲的元素是有序的。
  • 跟我學java編程—Set接口實現類TreeSet
    與HashSet集合採用通過hash算法來決定元素的存儲位置不同,TreeSet採用二叉樹的數據結構來存儲集合元素。圖 14-6 TreeSetTest示例輸出結果從輸出結果可以看出,TreeSet對加入元素默認按照升序進行排序。
  • java 基礎 之 集合 Map 與Set 與Queue
    事實上,Map提供了一個Entry內部類來封裝key-value對,而計算Entry存儲時則只考慮Entry封裝的key。從Java源碼來看, Java是先實現了Map,然後通過包裝一個所有value都為null的Map就實現了Set集合。
  • java常用的集合,這幾個你都會麼?
    大家好,這裡是java研究所。java中的集合比較多,今天我們來熟悉一下常用的幾個,了解其特點,用起來會更順手一些,下面先看下類圖1、List接口可以存放重複的元素,2個常用的實現類1.1、ArrayList放入順序和迭代的順序是一樣的,也就是說元素是有序的數組的特點是
  • 50道Java集合經典面試題(收藏版)
    看一下源碼的解釋吧:6.Collection是Java集合框架中的基本接口,如List接口也是繼承於它public interface List<E> extends Collection<E> {Collections是Java集合框架提供的一個工具類,其中包含了大量用於操作或返回集合的靜態方法。
  • 簡單的理解集合中的HashSet和HashTree幾個重寫方法
    通過調用hashcode和equals方法實現去重當我們HashSet裡面存的是字符串時,就能默認去重了,因為String已經重寫了hashcode和euqals方法public static void main(String[] args) {HashSet<String> set = new HashSet();set.add("java