標籤:
Set集合 類似於一個罐子,丟進"Set",集合裡的過個對象之間沒有明顯的順序。
Set集合與Collection集合基本上完全一樣,他沒有提供任何額外的方法。
實際上Set就是Collection,只是行為略有不同,Set不允許包含重複元素。允許包含值為null的元素,但最多隻能有一個null元素。
常用方法
按照定義,Set 介面繼承 Collection 介面,所有原始方法都是現成的,沒有引入新方法。具體的 Set 實作類別依賴添加的對象的 equals() 方法來檢查等同性。
各個方法的作用描述:
public int size() :返回set中元素的數目,如果set包含的元素數大於Integer.MAX_VALUE,返回Integer.MAX_VALUE;
public boolean isEmpty() :如果set中不含元素,返回true ;
public boolean contains(Object o) :如果set包含指定元素,返回true ;
public Iterator iterator() : 返回set中元素的迭代器,元素返回沒有特定的順序,除非set提高該保證的某些類的執行個體 ;
public boolean add(Object o) :如果set中不存在指定元素,則向set加入 ;
public boolean remove(Object o) :如果set中存在指定元素,則從set中刪除 ;
public boolean removeAll(Collection c) :如果set包含指定集合,則從set中刪除指定集合的所有元素 ;
public void clear() :從set中刪除所有元素;
原理分析
HashSet的元素存放順序和添加進去時候的順序沒有任何關係;而LinkedHashSet 則保持元素的添加順序;TreeSet則是對我們的Set中的元素進行排序存放。
一般來說,當要從集合中以有序的方式抽取元素時,TreeSet 實現就會有用處。為了能順利進行,添加TreeSet 的元素必須是可排序的。 而同樣需要對添加到TreeSet中的類對象實現 Comparable 介面的支援。
對於Comparable介面的實現。假定一棵樹知道如何保持 java.lang 封裝程式器類元素的有序狀態。一般說來,先把元素添加到 HashSet,再把集合轉換為 TreeSet 來進行有序遍曆會更快。這點和HashMap的使用非常的類似。
其實Set的實現原理是基於Map上面的。Set中很多實作類別和Map中的一些實作類別的使用上非常的相似。Map中的“鍵值對”,其中的 “鍵”是不能重複的。這個和Set中的元素不能重複一致,其實Set利用的就是Map中“鍵”不能重複的特性來實現的。
HashSet的巧妙實現:就是建立一個“鍵值對”,“鍵”就是我們要存入的對象,“值”則是一個常量。這樣可以確保, 我們所需要的儲存的資訊之是“鍵”。而“鍵”在Map中是不能重複的,這就保證了我們存入Set中的所有的元素都不重複。而判斷是否添加元素成功,則是通 過判斷我們向Map中存入的“鍵值對”是否已經存在,如果存在的話,那麼返回值肯定是常量:PRESENT ,表示添加失敗。如果不存在,返回值就為null 表示添加成功。
下面是一個測試類別
SetTest.java
package com.haixu.set;import java.util.HashSet;import java.util.Set;/* * Set 集合測試! * */public class SetTest { public static void main(String[] args) { //HashSet是Set的典型實現 Set<String> str1 = new HashSet<String>(); //添加一個字串 str1.add(new String("you will succcess!")); //再次添加一個字串對象 //因為兩個字串對象通過equals方法比較相等 //添加失敗將返回false boolean result = str1.add(new String("Hold on Haixu!")); System.out.println(result +"------->" + str1); }}
HashSet類:
1、HashSet不能重複儲存equals相同的資料 。原因就是equals相同,資料的散列碼也就相同(hashCode必須和equals相容)。大量相同的資料將存放在同一個散列單元所指向的鏈表中,造成嚴重的散列衝突,對尋找效率是災難性的。
2、HashSet的儲存是無序的 ,沒有前後關係,他並不是線性結構的集合。
3、hashCode必須和equals必須相容
當向HashSet集合中存入一個元素師,HsahSet會調用對象的hashCode方法來得到對象的hashCode值,然後根據該HashCode值決定該對象的HashSet的位置。
如果兩個元素通過equals()方法比較返回true,但他們的哈市COde方法返回值不相等,HashSet將會把他們存放在不同的位置!
這張圖片價值不菲呀!好好閱讀呀!
在網上找了好多的資料都沒有仔細詳細的講解HashSet 類,只能總計總結了!
HashSetTest.java
package com.haixu.set;/* * HashSet類的練習,測試對象的儲存位置 * */import java.util.HashSet;class A{public boolean equals(Object obj){return true;}}class B{public int hashCode(){return 1;}}class C{public int hashCode(){return 2;}public boolean equals(Object obj){return true;}}public class HashSetTest {public static void main(String[] args) {HashSet<Object> str1 = new HashSet<Object>();str1.add(new A());str1.add(new A());str1.add(new B());str1.add(new B());str1.add(new C());str1.add(new C());System.out.println(str1);}}
運行結果“[[email protected], [email protected],[email protected],[email protected],[email protected]]
HashSetTest2.java
package com.haixu.set;import java.util.HashSet;import java.util.Iterator;class R{int count;public R(int count){this.count = count;}public String toString(){return "R[count:" + count + "]";}public boolean equals(Object obj){if(this == obj){return true;}if(obj != null && obj.getClass() == R.class ){R r = (R)obj;if(r.count == this.count){return true;}}return false;}public int hashCode(){return this.count;}}public class HashSetTest2 {public static void main(String[] args) {HashSet<Object> hs =new HashSet<Object>();hs.add(new R(5));hs.add(new R(-3));hs.add(new R(9));hs.add(new R(-2));//列印HashSet集合,集合元素沒有重複System.out.println(hs);//取出一個元素Iterator it = (Iterator) hs.iterator();R first = (R)it.next();//為第一個元素count執行個體變數賦值first.count = -3;//再次輸出HashSet集合,集合沒有重複元素System.out.println(hs);//刪除count為-3的R對象hs.remove(new R(-3));//可以看出被刪除類一個對象System.out.println(hs);System.out.println("hs是否包含count為-3的R對象?" + hs.contains(new R(-3)));System.out.println("hs是否包含count為5的R對象?" + hs.contains(new R(5)));}}
TreeSet原理:
/*
* TreeSet儲存物件的時候, 可以排序, 但是需要指定排序的演算法
*
* Integer能排序(有預設順序), String能排序(有預設順序), 自訂的類儲存的時候出現異常(沒有順序)
*
* 如果想把自訂類的對象存入TreeSet進行排序, 那麼必須實現Comparable介面
* 在類上implement Comparable
* 重寫compareTo()方法
* 在方法內定義比較演算法, 根據大小關係, 返回正數負數或零
* 在使用TreeSet儲存物件的時候, add()方法內部就會自動調用compareTo()方法進行比較, 根據比較結果使用二叉樹形式進行儲存
*/
TreeSet是依靠TreeMap來實現的。
TreeSet是一個有序集合,TreeSet中的元素將按照升序排列,預設是按照自然排序進行排列,意味著TreeSet中的元素要實現Comparable介面。或者有一個自訂的比較子。
我們可以在構造TreeSet對象時,傳遞實現Comparator介面的比較子對象。
import java.util.Iterator;
import java.util.*;
public class TreeSetTest {
public static void main(String[] args) {
Set ts = new TreeSet();
ts.add("Haixu");
ts.add("success");
ts.add("you can!");
Iterator it = ts.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
輸出結果“Haixu, success,you can!
列印結果不是和先前加入的順序一樣,它是按照一個字母的排序法進行排序的。這是因為String 類實現了Comparable介面。
如果我們自己定義的一個類的對象要加入到TreeSet當中,那麼這個類必須要實現Comparable介面。
比較子
在使用Arrays對數組中的元素進行排序的時候,可以傳遞一個比較子。
在使用Collections對集合中的元素進行排序的時候,可以傳遞一個比較子。
那麼在使用TreeSet對加入到其中的元素進行排序的時候可以傳入一個比較子嗎?
public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<E,Object>(comparator)); }
通過查看它的構造方法就知道可以傳入一個比較子。
EnumSet 是一個與枚舉類型一起使用的專用 Set 實現。枚舉set中所有元素都必須來自單個枚舉類型(即必須是同類型,且該類型是Enum的子類)。
枚舉類型在建立 set 時顯式或隱式地指定。枚舉 set 在內部表示為位向量。 此表示形式非常緊湊且高效。此類的空間和時間效能應該很好,
足以用作傳統上基於 int 的“位標誌”的替換形式,具有高品質、型別安全的優勢。
如果指定的 collection 也是一個枚舉 set,則大量操作(如 containsAll 和 retainAll)也應運行得非常快。
由 iterator 方法返回的迭代器按其自然順序 遍曆這些元素(該順序是聲明枚舉常量的順序)。
返回的迭代器是弱一致的:它從不拋出 ConcurrentModificationException,也不一定顯示在迭代進行時發生的任何 set 修改的效果。
不允許使用 null 元素。試圖插入 null 元素將拋出 NullPointerException。但是,
試圖測試是否出現 null 元素或移除 null 元素將不會拋出異常。
像大多數 collection 一樣,EnumSet 是不同步的。如果多個線程同時訪問一個枚舉 set,
並且至少有一個線程修改該 set,則此枚舉 set 在外部應該是同步的。這通常是通過對自然封裝該枚舉 set 的對象執行同步操作來完成的。
如果不存在這樣的對象,則應該使用 Collections.synchronizedSet(java.util.Set) 方法來“封裝”該 set。
最好在建立時完成這一操作,以防止意外的非同步訪問:
Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(Foo.class));
實現注意事項:所有基本操作都在固定時間內執行。雖然並不保證,但它們很可能比其 HashSet 副本更快。
如果參數是另一個 EnumSet 執行個體,則諸如 addAll() 和 AbstractSet.removeAll(java.util.Collection) 之類的大量操作
也會在固定時間內執行。
注意1:不允許使用 null 元素。試圖插入 null 元素將拋出 NullPointerException。但是,
試圖測試是否出現 null 元素或移除 null 元素將不會拋出異常。
注意2:EnumSet是不同步的。不是安全執行緒的。
注意3:EnumSet的本質就為枚舉類型定製的一個Set,且枚舉set中所有元素都必須來自單個枚舉類型。
注意4:關於EnumSet的儲存,文檔中提到是這樣的。 “枚舉 set 在內部表示為位向量。”
我想它應該是用一個bit為來表示的於之對應的枚舉變數是否在集合中。
比如:0x1001
假如約定從低位開始,就表示第0個,和第三個枚舉類型變數在EnumSet中。
這樣的話空間和時間效能也就應該很好。
注意5:至於Enum的枚舉常量的位置(序數)可以用Enum的ordinal()方法得到。
注意6:在jdk內部可以一個數組的形式一個枚舉的枚舉常量。
下面是來自來JDK中RegularEnumSet.java的一個樣本
private static <E extends Enum<E>> E[] getUniverse(Class<E> elementType) {
return SharedSecrets.getJavaLangAccess()
.getEnumConstantsShared(elementType);
}
注意7:元素屬於哪種枚舉類型必須在建立 set 時顯式或隱式地指定.
注意8:關於枚舉類型的更多知識可參考《枚舉類型》
Enumset是個虛類,我們只能通過它提供的靜態方法來返回Enumset的實作類別的執行個體。返回EnumSet的兩種不同的實現:如果EnumSet大小小於64,就返回RegularEnumSet執行個體(當然它繼承自EnumSet),這個EnumSet實際上至用了一個long來儲存這個EnumSet。如果 EnumSet大小大於等於64,則返回JumboEnumSet執行個體,它使用一個long[]來儲存。這樣做的好處很明顯: 大多數情況下返回的RegularEnumSet效率比JumboEnumSet高很多。
執行個體1import java.util.Comparator;import java.util.EnumSet;import java.util.Random;import java.util.concurrent.SynchronousQueue;import java.util.concurrent.TimeUnit;public class Test { /** * @param args */ public static void main(String[] args) { System.out.println("EnumSet.noneOf"); EnumSet<Student> set=EnumSet.noneOf(Student.class); set.add(Student.HARRY); set.add(Student.ROBBIE); set.add(Student.ROBIN); for(Student p:set) System.out.println(p); set.clear(); System.out.println("EnumSet.allOf"); set=EnumSet.allOf(Student.class); for(Student p:set) System.out.println(p); set.clear(); System.out.println("EnumSet.Of one"); set=EnumSet.of(Student.ROBIN); for(Student p:set) System.out.println(p); System.out.println("EnumSet.Of two"); set=EnumSet.of(Student.ROBIN,Student.HARRY); for(Student p:set) System.out.println(p); }}enum Student{ ROBIN("robin"), HARRY("harry",40), ROBBIE("robbie"); String name; int age; private Student(String name) { this(name,0); } private Student(String name,int age) { this.name=name; this.age=age; } public String toString() { return name; }}
瘋狂Java學習筆記(36)-----------Set集合