【由淺入深理解java集合(二)】——集合 Set

來源:互聯網
上載者:User

標籤:可變對象   bool   cimage   對比   推薦   覆蓋   equals   程式   快速   

上一篇文章介紹了Set集合的通用知識。Set集合中包含了三個比較重要的實作類別:HashSet、TreeSet和EnumSet。本篇文章將重點介紹這三個類。

  

一、HashSet類HashSet簡介

HashSet是Set介面的典型實現,實現了Set介面中的所有方法,並沒有添加額外的方法,大多數時候使用Set集合時就是使用這個實作類別。HashSet按Hash演算法來儲存集合中的元素。因此具有很好的存取和尋找效能。

HashSet特點

1.不能保證元素的排列順序,順序可能與添加順序不同,順序也有可能發生變化。
2.HashSet不是同步的,如果多個線程同時訪問一個HashSet,則必須通過代碼來保證其同步。
3.集合元素值可以是null。
除此之外,HashSet判斷兩個元素是否相等的標準也是其一大特點。HashSet集合判斷兩個元素相等的標準是兩個對象通過equals()方法比較相等,並且兩個對象的hashCode()方法傳回值也相等。

寫到這裡,我們就要介紹下equals()和hashCode()方法了。

equals()和hashCode()equals()

equals() 的作用是** 用來判斷兩個對象是否相等。**

equals() 定義在JDK的Object.java中。通過判斷兩個對象的地址是否相等(即,是否是同一個對象)來區分它們是否相等。源碼如下:

1 public boolean equals(Object obj) {2        return (this == obj);3    }
既然Object.java中定義了equals()方法,這就意味著所有的Java類都實現了equals()方法,所有的類都可以通過equals()去比較兩個對象是否相等。 但是,使用預設的“equals()”方法,等價於“==”方法。我們也可以在Object的子類中重寫此方法,自訂“equals()”方法,在其中定義自己的判斷邏輯,如果滿足則返回true,不滿足則返回false。下面我們自訂一個類 Person,並認為年齡,身高相等的兩個Person對象,equals()方法比較結果相等。
 1 public class Person { 2     public int age; 3     public int height; 4     @Override 5     public boolean equals(Object obj) { 6         if (this == obj) 7             return true; 8         if (obj == null) 9             return false;10         if (getClass() != obj.getClass())11             return false;12         Person other = (Person) obj;13         if (age != other.age)14             return false;15         if (height != other.height)16             return false;17         return true;18     }19 }

 


 1 public class EqualTest { 2   public static void main(String[] args){ 3       Person p1 = new Person(); 4       Person p2 =new Person(); 5       System.out.println(p1.equals(p2)); 6   } 7  8 }

輸出結果:

true

  

下面根據“類是否覆蓋equals()方法”,將它分為2類。
(01) 若某個類沒有覆蓋equals()方法,當它的通過equals()比較兩個對象時,實際上是比較兩個對象是不是同一個對象。這時,等價於通過“==”去比較這兩個對象,即兩個對象的記憶體位址是否相同。
(02) 我們可以覆蓋類的equals()方法,來讓equals()通過其它方式比較兩個對象是否相等。通常的做法是:若兩個對象的內容相等,則equals()方法返回true;否則,返回fasle。

hashCode()

hashCode() 的作用是擷取雜湊碼,也稱為散列碼;它實際上是返回一個int整數。這個雜湊碼的作用是確定該對象在雜湊表中的索引位置。

hashCode() 定義在JDK的Object.java中,這就意味著Java中的任何類都包含有hashCode() 函數。雖然,每個Java類都包含hashCode() 函數。但是,僅僅當建立某個“類"的散列表時,該類的hashCode() 才有用。更通俗地說就是建立包含該類的HashMap,Hashtable,HashSet集合時,hashCode() 才有用。因為HashMap,Hashtable,HashSet就是散列表集合。
在散列表中,hashCode()作用是:確定該類的每一個對象在散列表中的位置;其它情況下類的hashCode() 沒有作用。在散列表中hashCode() 的作用是擷取對象的散列碼,進而確定該對象在散列表中的位置。

hashCode()也分兩種情況。一種是Object類中預設的方法,另一種是在子類中重寫的方法。
(01) 若某個類沒有覆蓋hashCode()方法,當它的通過hashCode()比較兩個對象時,實際上是比較兩個對象是不是同一個對象。這時,等價於通過“==”去比較這兩個對象,即兩個對象的記憶體位址是否相同。
(02) 我們可以覆蓋類的hashCode()方法,來讓hashCode()通過其它方式比較兩個對象是否相等。通常的做法是:若兩個對象的內容相等,則hashCode()方法返回true;否則,返回fasle。

通過對以上兩個方法的瞭解。我們可以接下來學習HashSet集合中如何判斷兩個元素是否相等?

HashSet中判斷集合元素相等

兩個對象比較 具體分為如下四個情況:
1.如果有兩個元素通過equal()方法比較返回false,但它們的hashCode()方法返回不相等,HashSet將會把它們儲存在不同的位置。

2.如果有兩個元素通過equal()方法比較返回true,但它們的hashCode()方法返回不相等,HashSet將會把它們儲存在不同的位置。

3.如果兩個對象通過equals()方法比較不相等,hashCode()方法比較相等,HashSet將會把它們儲存在相同的位置,在這個位置以鏈表式結構來儲存多個對象。這是因為當向HashSet集合中存入一個元素時,HashSet會調用對象的hashCode()方法來得到對象的hashCode值,然後根據該hashCode值來決定該Object Storage Service在HashSet中儲存位置。

4.如果有兩個元素通過equal()方法比較返回true,但它們的hashCode()方法返回true,HashSet將不予添加。

HashSet判斷兩個元素相等的標準:兩個對象通過equals()方法比較相等,並且兩個對象的hashCode()方法傳回值也相等。

注意:HashSet是根據元素的hashCode值來快速定位的,如果HashSet中兩個以上的元素具有相同的hashCode值,將會導致效能下降。所以如果重寫類的equals()方法和hashCode()方法時,應盡量保證兩個對象通過hashCode()方法傳回值相等時,通過equals()方法比較返回true。

LinkedHashSet類

LinkedHashSet是HashSet對的子類,也是根據元素的hashCode值來決定元素的儲存位置,同時使用鏈表維護元素的次序,使得元素是以插入的順序來儲存的。當遍曆LinkedHashSet集合裡的元素時,LinkedHashSet將會按元素的添加順序來訪問集合裡的元素。但是由於要維護元素的插入順序,在效能上略低與HashSet,但在迭代訪問Set裡的全部元素時有很好的效能。
注意:LinkedHashSet依然不允許元素重複,判斷重複標準與HashSet一致。

補充:HashSet的實質是一個HashMap。HashSet的所有集合元素,構成了HashMap的key,其value為一個靜態Object對象。因此HashSet的所有性質,HashMap的key所構成的集合都具備。可以參考後續文章中HashMap的相關內容進行比對。

二、TreeSet類TreeSet簡介

TreeSet是SortedSet介面的實作類別,正如SortedSet名字所暗示的,TreeSet可以確保集合元素處於排序狀態。此外,TreeSet還提供了幾個額外的方法。

TreeSet的方法
comparator():返回對此 set 中的元素進行排序的比較子;如果此 set 使用其元素的自然順序,則返回null。first():返回此 set 中當前第一個(最低)元素。last(): 返回此 set 中當前最後一個(最高)元素。lower(E e):返回此 set 中嚴格小於給定元素的最大元素;如果不存在這樣的元素,則返回 null。higher(E e):返回此 set 中嚴格大於給定元素的最小元素;如果不存在這樣的元素,則返回 null。subSet(E fromElement, E toElement):返回此 set 的部分視圖,其元素從 fromElement(包括)到 toElement(不包括)。headSet(E toElement):返回此 set 的部分視圖,其元素小於toElement。tailSet(E fromElement):返回此 set 的部分視圖,其元素大於等於 fromElement。

  

TreeSet的排序方式

TreeSet中所謂的有序,不同於之前所講的插入順序,而是通過集合中元素屬性進行排序方式來實現的。
TreeSet支援兩種排序方法:自然排序和定製排序。在預設情況下,TreeSet採用自然排序。

1.自然排序

在講自然排序之前,要先講一下Comparable介面

Java提供了一個Comparable介面,該介面裡定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現該介面的類必須實現該方法,實現了該介面的類的對象就可以比較大小了。當一個對象調用該方法與另一個對象比較時,例如obj1.compareTo(obj2),如果該方法返回0,則表明兩個對象相等;如果該方法返回一個整數,則表明obj1大於obj2;如果該方法返回一個負整數,則表明oj1小於obj2。

TreeSet會調用集合中元素所屬類的compareTo(Object obj)方法來比較元素之間的大小關係,然後將集合元素按升序排列,即把通過compareTo(Object obj)方法比較後比較大的的往後排。這種方式就是自然排序。

Java的一些常用類已經實現了Comparable介面,並提供了比較大小的標準。例如,String按字串的UNICODE值進行比較,Integer等所有數實值型別對應的封裝類按它們的數值大小進行比較。
除了這些已經實現Comparable介面類之外,如果試圖把一個對象添加到TreeSet時,則該對象的類必須實現Comparable介面,否則就會出現異常。
注意:TreeSet中只能添加同一種類型的對象,否則無法比較,會出現異常。

TreeSet中判斷集合元素相等

對於TreeSet集合而言,判斷兩個對象是否相等的唯一標準是:兩個對象通過compareTo(Object obj)方法比較是否返回0——如果通過compareTo(Object obj)方法比較返回0,TreeSet則會認為它們相等,不予添加入集合內;否則就認為它們不相等,添加到集合內。
TreeSet是根據紅/黑樹狀結構結構找到集合元素的儲存位置。

2.定製排序

TreeSet的自然排序是根據集合元素中compareTo(Object obj)比較的大小,以升序排列。而定製排序是通過Comparator介面的協助。該介面包含一個int compare(T o1,T o2)方法,該方法用於比較o1,o2的大小:如果該方法返回正整數,則表明o1大於o2;如果該方法返回0,則表明o1等於o2;如果該方法返回負整數,則表明o1小於o2。
如果要實現定製排序,則需要在建立TreeSet時,調用一個帶參構造器,傳入Comparator對象。並有該Comparator對象負責集合元素的排序邏輯,集合元素可以不必實現Comparable介面。下面具體示範一下這種用法:

 1 public static void main(String[] args){ 2         Person p1 = new Person(); 3         p1.age =20; 4         Person p2 =new Person(); 5         p2.age = 30; 6         Comparator<Person> comparator = new Comparator<Person>() { 7  8             @Override 9             public int compare(Person o1, Person o2) {10                 //年齡越小的排在越後面11                 if(o1.age<o2.age){12                     return 1;13                 }else if(o1.age>o2.age){14                     return -1;15                 }else{16                     return 0;17                 }18                 19             }20         };21         TreeSet<Person> set = new TreeSet<Person>(comparator);22         set.add(p1);23         set.add(p2);24         System.out.println(set);25     }
[Person[age=30], Person[age=20]]

  

總結:無論使用自然排序還是定製排序,都可以通過自訂比較邏輯實現各種各樣的排序方式。

注意:如果向TreeSet中添加了一個可變對象後,並且後面程式修改了該可變對象的執行個體變數,這將導致它與其他對象的大小順序發生了改變,但TreeSet不會再次調整它們。下面程式示範這一現象:

 1 TreeSet<Person> set = new TreeSet<Person>(); 2         Person p1 = new Person(); 3         p1.setAge(10); 4         Person p2 =new Person(); 5         p2.setAge(30); 6         Person p3 =new Person(); 7         p3.setAge(40); 8         set.add(p1); 9         set.add(p2);10         set.add(p3);11         System.out.println("初始年齡排序");12         System.out.println(set);13         //p1的年齡修改成50 最大14         p1.age = 60;15         System.out.println("修改p1年齡後集合排序");16         System.out.println(set);17         p2.age = 40;18         System.out.println("修改p2年齡後集合排序");19         System.out.println(set);20         Person p4 = new Person();

其中Person實現Comparable介面,將Person對象按照年齡從小到大升序排列。
輸出結果:

初始年齡排序[Person[age=10], Person[age=30], Person[age=40]]修改p1年齡後集合排序[Person[age=60], Person[age=30], Person[age=40]]修改p2年齡後集合排序[Person[age=60], Person[age=40], Person[age=40]]

  

可以看到並沒有發生變化,而且如果修改後進行元素刪除操作可能會不成功,具體比較複雜。總之,推薦不要修改放入TreeSet集合中元素的關鍵執行個體變數。
補充:TreeSet也是非安全執行緒的。

三、EnumSet類EnumSet簡介

EnumSet是一個專為枚舉類設計的集合類,EnumSet中的所有元素都必須是指定枚舉類型的枚舉值,該枚舉類型在建立EnumSet時顯示或隱式地指定。EnumSet的集合元素也是有序的,EnumSet以枚舉值在EnumSet類內的定義順序來決定集合元素的順序。

EnumSet特點

1.EnumSet集合不允許加入null元素。EnumSet中的所有元素都必須是指定枚舉類型的枚舉值。
2.EnumSet類沒有暴露任何構造器來建立該類的執行個體,程式應該通過它提供的類方法來建立EnumSet對象。

EnumSet沒有其他額外增加的方法,只是增加了一些建立EnumSet對象的方法。

EnumSet建立對象的方法

 

  
補充:EnumSet 也是非安全執行緒的。

 

四、HashSet、TreeSet和EnumSet的效能對比

EnumSet效能>HashSet效能>LinkedHashSet>TreeSet效能

EnumSet內部以位向量的形式儲存,結構緊湊、高效,且只儲存枚舉類的枚舉值,所以最高效。HashSet以hash演算法進行位置儲存,特別適合用於添加、查詢操作。LinkedHashSet由於要維護鏈表,效能比HashSet差點,但是有了鏈表,LinkedHashSet更適合於插入、刪除以及遍曆操作。而TreeSet需要額外的紅/黑樹狀結構演算法來維護集合的次序,效能最次。

但是具體使用要考慮具體的使用情境。
當需要一個特定排序的集合時,使用TreeSet集合。
當需要儲存枚舉類的枚舉值時,使用EnumSet集合。
當經常使用添加、查詢操作時,使用HashSet。
當經常插入排序或使用刪除、插入及遍曆操作時,使用LinkedHashSet。

後續文章將對java集合中的具體實作類別進行深入瞭解。有興趣的話可以觀看後續內容,進一步瞭解java集合內容。

由淺入深理解java集合(一)——集合架構 Collction、Map
由淺入深理解java集合(三)——集合 List
由淺入深理解java集合(四)——集合 Queue
由淺入深理解java集合(五)——集合 Map
由淺入深理解java集合(六)——集合增刪改查的細節、效能及選擇推薦(待更新)

轉載連結:https://www.jianshu.com/p/9081017a2d67

 

【由淺入深理解java集合(二)】——集合 Set

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.