Set接口深入剖析之HashSet、LinkedHashSet和TreeSet
一、基礎(chǔ)知識
- Set接口:存儲無序、不可重復(fù)的數(shù)據(jù)
- HashSet:主要實現(xiàn)類,線程不安全,可以存儲null值
- LinkedHashSet:是HashSet的子類,遍歷內(nèi)部的數(shù)據(jù)時,可以按照添加的順序遍歷
- TreeSet:可以按照添加的對象指定屬性,進行排序
二、HashSet
基于JDK 1.8 分析
定義
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.SerializableHashSet繼承AbstractSet類,實現(xiàn)Set、Cloneable、Serializable接口
基本屬性
private transient HashMap<E,Object> map; //定義一個Object對象作為HashMap的value private static final Object PRESENT = new Object();
HashSet中數(shù)據(jù)是存放在HashMap中,HashSet基于HashMap,對數(shù)據(jù)的訪問基本用HashMap的方法,另外存入Set中的數(shù)據(jù)本身是無序的,維護訪問順序沒有意義
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}HashSet是可克隆對象和進行序列化 ,其內(nèi)部的數(shù)據(jù)存儲區(qū)通過一個transient修飾的HashMap維護,調(diào)用了HashMap的構(gòu)造函數(shù)完成。主要的參數(shù)是基礎(chǔ)容量為16個單位,加載因子是0.75
方法
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
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);
}
/**
* Returns an iterator over the elements in this set. The elements
* are returned in no particular order.
*
* @return an Iterator over the elements in this set
* @see ConcurrentModificationException
*/
public Iterator<E> iterator() {
return map.keySet().iterator();
} contains()判斷某個元素是否存在于HashSet()中,存在返回true,否則返回false,底層調(diào)用containsKey判斷HashMap的key值是否為空 底層調(diào)用HashMap的keySet返回所有的key,反映了HashSet中的所有元素都是保存在HashMap的key中,value則是使用的PRESENT對象,該對象為static final
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 實例的淺表副本,并沒有復(fù)制這些元素
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相等時讓舊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不允許重復(fù)的元素加入,因為只要key的equals方法判斷為true時會發(fā)生value的替換,即當(dāng)兩個hashcode相同但key不相等的entry插入時,仍然會連成一個鏈表,長度超過8時依然會和hashmap一樣擴展成紅黑樹,當(dāng)add方法發(fā)生沖突時,如果key相同,則替換value,如果key不同,則連成鏈表
小結(jié)
- HashSet有以下特點
- 不能保證元素的排列順序,順序有可能發(fā)生變化
- 不是同步的
- 集合元素可以是null,但只能放入一個null
- 當(dāng)向HashSet結(jié)合中存入一個元素時,HashSet會調(diào)用該對象的hashCode()方法來得到該對象的hashCode值,然后根據(jù) hashCode值來決定該對象在HashSet中存儲位置
- HashSet集合判斷兩個元素相等的標(biāo)準(zhǔn)是兩個對象通過equals方法比較相等,并且兩個對象的hashCode()方法返回值相等
注:如果要把一個對象放入HashSet中,重寫該對象對應(yīng)類equals方法,也應(yīng)該重寫其hashCode()方法:其規(guī)則是如果兩個對象通過equals方法比較返回true時,其hashCode也應(yīng)該相同。另對象中用作equals比較標(biāo)準(zhǔn)的屬性,都應(yīng)用來計算 hashCode的值
三、LinkedHashSet
LinkedHashSet集合同樣是根據(jù)元素的hashCode值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起 來像是以插入順序保存的,當(dāng)遍歷該集合時候,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);
}四個構(gòu)造函數(shù)調(diào)用的是同一個父類構(gòu)造函數(shù),該構(gòu)造函數(shù)是一個包內(nèi)私有構(gòu)造函數(shù),它只能被LinkedHashSet使用,該構(gòu)造函數(shù)需要初始容量,負(fù)載因子和 boolean類型的啞值等參數(shù),這個啞參數(shù)只是用來區(qū)別這個構(gòu)造函數(shù)與HashSet的其他擁有初始容量和負(fù)載因子參數(shù)的構(gòu)造函數(shù)
注:啞值:沒有什么用處的參數(shù),作為標(biāo)記
小結(jié)
LinkedHashSet直接繼承自HashSet,能夠維護基礎(chǔ)的有序性,LinkedHashSet并沒有自己的方法,所有方法都繼承自它的父類HashSet,因此對LinkedHashSet的所有操作方式就像對HashSet操作一樣,唯一的不同是內(nèi)部使用不同的對象去存儲元素。在HashSet中,插入的元素是被當(dāng)做HashMap的鍵來保存的,而在LinkedHashSet中被看作是LinkedHashMap的鍵
換句話說 LinkedHashSet繼承自HashSet,源碼更少、更簡單,唯一的區(qū)別是LinkedHashSet內(nèi)部使用的是LinkHashMap
特點:
- 有序,按照插入順序
- 不是同步的
- 集合元素可以是null,但只能放入一個null
- 沒有set和get方法 只能通過迭代器取值
LinkedHashSet在迭代訪問Set中的全部元素時,性能比HashSet好,但是插入時性能稍微遜色于HashSet
四、TreeSet
定義
TreeSet與TreeMap實現(xiàn)類似
TreeMap是一個有序的二叉樹,TreeSet同樣也是一個有序的二叉樹,它的作用是提供有序的Set集合。TreeSet繼承自AbstractSet,實現(xiàn)了Set接口、Cloneable和Serializable、NavigableSet接口。其內(nèi)部主要是通過一個NavigableMap的map維護數(shù)據(jù)存儲
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable構(gòu)造方法
//默認(rèn)構(gòu)造方法
public TreeSet() {
this(new TreeMap<E,Object>());
}
//構(gòu)造包含指定 collection 元素的新 TreeSet,按照其元素的自然順序進行排序。
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
//構(gòu)造新的空 TreeSet,根據(jù)指定比較器進行排序。
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
//構(gòu)造與指定有序 set 具有相同映射關(guān)系和相同排序的新 TreeSet
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}主要方法
1.add:將指定的元素添加到此 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) {
//空樹時,判斷節(jié)點是否為空
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;
//非空樹,根據(jù)傳入比較器進行節(jié)點的插入位置查找
if (cpr != null) {
do {
parent = t;
//節(jié)點比根節(jié)點小,則找左子樹,否則找右子樹
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);
}
//查找的節(jié)點為空,直接插入,默認(rèn)為紅節(jié)點
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//插入后進行紅黑樹調(diào)整
fixAfterInsertion(e);
size++;
modCount++;
return null;
} get:獲取元素
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}與put的流程類似,只把插入換成了查找
ceiling:返回此 set 中大于等于給定元素的最小元素;如果不存在這樣的元素,則返回 null
太多了直接上腦圖:

自然排序與定制排序
TreeSet支持兩種排序方式,自然排序 和 定制排序
小結(jié)
- TreeSet內(nèi)部是通過TreeMap構(gòu)造出來的
- 有序,按照比較器排序,并非按照插入順序
- 不是同步的
- 集合元素可以是null,但只能放入一個null
- 沒有set和get方法 只能通過迭代器取值
- TreeSet是SortedSet接口的唯一實現(xiàn)類,TreeSet可以確保集合元素處于排序狀態(tài)
總結(jié):HashSet、LinkedHashSet和TreeSet,三者其內(nèi)部都是基于Map來實現(xiàn)的。TreeSet和LinkedHashSet分別使用了TreeMap和LinkedHashMap來控制訪問數(shù)據(jù)的有序性。都屬于Set的范疇,都是沒有重復(fù)元素的集合。基于HashMap和TreeMap,也都是非線程安全的
到此這篇關(guān)于Set接口深入剖析之HashSet、LinkedHashSet和TreeSet的文章就介紹到這了,更多相關(guān)Set接口深入剖析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java利用Dijkstra算法求解拓?fù)潢P(guān)系最短路徑
迪杰斯特拉算法(Dijkstra)是由荷蘭計算機科學(xué)迪家迪杰斯特拉于1959年提出的,因此又叫狄克斯特拉算法。本文將利用迪克斯特拉(Dijkstra)算法求拓?fù)潢P(guān)系最短路徑,感興趣的可以了解一下2022-07-07
解決spring @ControllerAdvice處理異常無法正確匹配自定義異常
這篇文章主要介紹了解決spring @ControllerAdvice處理異常無法正確匹配自定義異常的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
Java?HashTable與Collections.synchronizedMap源碼深入解析
HashTable是jdk?1.0中引入的產(chǎn)物,基本上現(xiàn)在很少使用了,但是會在面試中經(jīng)常被問到。本文就來帶大家一起深入了解一下Hashtable,需要的可以參考一下2022-11-11
Java中將List列表轉(zhuǎn)換為字符串的三種方法
這篇文章主要介紹了如何在 Java中將List 轉(zhuǎn)換為 String,接下來使用Java 8 Streams Collectors api和String.join()方法將帶有逗號分隔符或自定義分隔符的集合轉(zhuǎn)換為字符串,需要的朋友可以參考下2025-04-04

