java集合(4)-Set集合
Set集合,類似于一個罐子,程序可以把多個對象"丟進"Set集合,而Set集合通常不能記住每個元素的添加順序.Set集合與Collection基本相同,沒有提供任何額外的方法.實際上Set就是Collection,只是行為有所不同(Set不允許有重復元素)
Set集合不允許包含相同的元素,如果試圖把兩個相同的元素添加入同一個Set集合中,則添加操作失敗,add()返回false,且新元素不會被加入.
上面介紹Set的通用知識,因此完全適合后面介紹的HashSet,TreeSet和EnumSet三個實現類,只是這三個實現類各有特色.
一? HashSet
HashSet是Set接口的典型實現,大多數時候使用的Set集合時就是使用這個實現類.HashSet按Hash算法來存儲集合中的元素,因此具有良好的存取和查找性能。
HashSet具有以下特點:
1.不能保證元素的排列順序,順序可能與添加順序不同,順序也有可能發(fā)生變化。
2.HashSet不是同步的,如果多個線程同時訪問一個HashSet。假設有兩個或者以上的線程同時修改了HashSet集合時,則必須通過代碼來保證其同步。
3.集合元素可以是null.
當使用HashSet集合來存入一個元素時,HashSet會調用該對象的hashCode()方法來得到該對象的hashCode()值,然后根據該hashCode值決定該對象在HashSet中的存儲位置。如果有兩個元素通過equals()方法比較返回true,但是它們的hashCode()方法返回值不相等,HashSet將會把它們存入不同的位置,依然可以添加成功。
也就是說,HashSet集合判斷兩個元素相等的標準是兩個對象通過equals()方法比較相等,并且兩個對象的hashCode()方法返回值也相等。
示例代碼:類A,B,C,分別重寫了equals()方法,hashCode()方法這兩個中的一個或者兩個。
package com.j1803.collectionOfIterator;import java.util.HashSet; import java.util.Set;//類A重寫了equals()方法,總是返回true,但沒有重寫hashCode()方法. class A{@Overridepublic boolean equals(Object obj){return true;} }//類B重寫了hashCode()方法,總是返回2,但沒有重寫equals()方法 class B{@Overridepublic int hashCode(){return 2;} } //類C重寫了equals()方法,總是返回true,重寫了hashCode()方法,總是返回2 class C{@Overridepublic boolean equals(Object obj){return true;}@Overridepublic int hashCode(){return 2;} } public class HashSetTest {public static void main(String[] args) {Set book=new HashSet();HashSet books=new HashSet();A a=new A();B b=new B();C c=new C();books.add(a);books.add(a);books.add(b);books.add(b);books.add(c);books.add(c);System.out.println(books);} } [com.j1803.collectionOfIterator.B@2, com.j1803.collectionOfIterator.A@4554617c]Process finished with exit code 0?
package com.j1803.collectionOfIterator;import java.util.HashSet; import java.util.Set;//類A重寫了equals()方法,總是返回true,但沒有重寫hashCode()方法. class A{@Overridepublic boolean equals(Object obj){return true;} }//類B重寫了hashCode()方法,總是返回2,但沒有重寫equals()方法 class B{@Overridepublic int hashCode(){return 2;} } //類C重寫了equals()方法,總是返回true,重寫了hashCode()方法,總是返回2 class C{/* @Overridepublic boolean equals(Object obj){return true;}*/@Overridepublic int hashCode(){return 2;} } public class HashSetTest {public static void main(String[] args) {Set book=new HashSet();HashSet books=new HashSet();A a=new A();B b=new B();C c=new C();books.add(a);books.add(a);books.add(b);books.add(b);books.add(c);books.add(c);book.add(new A());book.add(new A());book.add(new B());book.add(new B());Boolean flag1=book.add(new C());System.out.println(flag1);Boolean flag2=book.add(new C());System.out.println(flag2);System.out.println(book);System.out.println(books);} }true
true
[com.j1803.collectionOfIterator.B@2, com.j1803.collectionOfIterator.B@2, com.j1803.collectionOfIterator.C@2, com.j1803.collectionOfIterator.C@2, com.j1803.collectionOfIterator.A@74a14482, com.j1803.collectionOfIterator.A@1540e19d]
[com.j1803.collectionOfIterator.B@2, com.j1803.collectionOfIterator.C@2, com.j1803.collectionOfIterator.A@4554617c]
Process finished with exit code 0
package com.j1803.setTest; import java.util.HashSet; class A{@Overridepublic boolean equals(Object arg0) {// TODO Auto-generated method stubreturn true;} } class B{@Overridepublic int hashCode() {// TODO Auto-generated method stubreturn 2;}} class C{@Overridepublic boolean equals(Object obj) {// TODO Auto-generated method stubreturn true;}@Overridepublic int hashCode() {// TODO Auto-generated method stub//注意hashCode()返回為1不是與B類中一樣返回為2return 1;} } public class HashSetTest {public static void main(String[] args) {HashSet books1=new HashSet();books1.add(new A());books1.add(new A());books1.add(new B());books1.add(new B());books1.add(new C());books1.add(new C());System.out.println(books1);} [com.j1803.setTest.A@7852e922, com.j1803.setTest.C@1, com.j1803.setTest.B@2, com.j1803.setTest.B@2, com.j1803.setTest.A@4e25154f]
?
?
注意點:當把一個對象放入HashSet中時,如果需要重寫該對象對應類的equals()方法,則也應該重寫其hashCode()方法。規(guī)則是:如果兩個對象通過equals()方法比較返回true,則這兩個對象的hashCode()值也應該相同。
如果兩個對象通過equals()方法比較返回true,但這兩個對象的hashCode()方法返回值不同,這將導致HashSet會把這兩個對象保存在Hash表中不同的位置,從而使兩個對象都可以添加成功這就與Set集合的規(guī)則相沖突了,
如果兩個對象的hashCode()方法返回的hashCode()值相同,但它們通過equals()方法比較返回false時將更麻煩:因為兩個對象的hashCode()值相同,HashSet將試圖將它們保存在同一個位置,但又不行(否則只剩下一個對象)所以在實際上會在這個位置用鏈式結構來保存多個對象;而HashSet訪問集合元素時也是根據元素的hashCode值來快速定位的,如果HashSet中兩個以上的元素具有相同的hashCode值,將導致性能下降。
hashCode()方法對于HashSet的重要性(實際上,對象的hashCode值對于后面的HashMap同樣重要),下面給出重寫hashCode()方法的基本原則。
在程序運行過程中,同一個對象多次調用hashCode()方法應該返回相同的值。
當兩個對象通過equals()方法比較返回true時,這兩個對象的hashCode()方法也應該返回相等的值。
對象中用作equals()方法比較標準的實例變量,都應該用于計算hashCode值。
下面給出重寫hashCode()方法的一般幾個步驟。
?
二? LinkedHashSet類
????? HashSet還有一個子類LinkedHashSet,LinkHashSet集合也是根據元素的hashCode的值來決定元素的存儲位置的,但它使用鏈表維護元素的次序,這樣使得元素看起來是以插入的順序保存的。也就是說,當遍歷LinkedHashSet集合里的元素時,LinkedHashSet將會按照元素的添加順序來訪問集合里的元素。
LinkedHashSet需要維護元素的插入順序,因此性能略低于HashSet的性能,但在迭代訪問Set里的全部元素時將有很好的性能,因為它是以鏈表來維護內部順序的。
package com.j1803.setTest; import java.util.LinkedHashSet; public class LinkedHashSetTest {public static void main(String[] args) {LinkedHashSet book=new LinkedHashSet();book.add("AAAAA");book.add("BBBBB");book.add("CCCCC");book.add("DDDDD");System.out.println(book);}} [AAAAA, BBBBB, CCCCC, DDDDD]輸出LinkedHashSet集合的元素時,元素的順序總是與添加順序一致。
雖然LinkedHashSet使用了鏈表記錄集合元素的添加順序,但LinkedHashSet依然是HashSet,因此它依然不允許集合元素重復。
三? TreeSet類
TresSet是SortedSet接口的實現類,正如SortedSet名字所暗示的,TreeSet可以確保集合元素處于排序狀態(tài)。與HashSet集合相比,TreeSet還提供了如下幾個額外的方法
Comparator comparator():如果TreeSet采用了定制排序,則該方法返回定制排序所使用的Comparator;如果TreeSet采用了自然排序,則返回null.
Object first():返回集合的第一個元素。
Object last():返回集合中的最后一個元素。
Object lower(Object e):返回集合中位于指定元素之前的元素(也就是小于指定元素的最大元素,參考元素不需要是TreeSet集合里的元素)。
Object higher(Object e):返回集合中位于指定元素之后的元素(也就是大于指定元素的最小元素,參考元素不需要是TreeSet集合里的元素)。
Object subSet(Object fromElement,Object toElement):返回此Set的子集合,范圍從fromElement(包含)到toElement(不包含)。
SortedSet headSet(Object toElement):返回此Set集合的子集,由小于toElement的元素組成。
SortedSet tailSet(Object fromElement):返回此Set集合的子集,由大于或等于fromElement的元素組成。
package com.j1803.setTest; import java.util.TreeSet; public class TreeSetTest {public static void main(String[] args) {TreeSet num=new TreeSet();num.add(-5);num.add(45);num.add(78);num.add(-13);num.add(40);num.add(99);num.add(0);System.out.println(num);//輸出集合的第一個元素System.out.println("輸出集合的第一個元素"+num.first());//輸出集合的最后一個元素System.out.println("輸出集合的最后一個元素"+num.last());//輸出小于50最大的元素System.out.println("輸出小于50的最大的元素"+num.lower(50));//輸出大于50的最小元素System.out.println("輸出大于50的最小元素"+num.higher(50));//輸出50到80之間的元素System.out.println("輸出10到80之間的元素"+num.subSet(10, 80));//輸出小于50的元素treeSet集合System.out.println("輸出小于50的元素"+num.headSet(50));//輸出大于50的元素的treeSet集合System.out.println("輸出大于50的元素的treeSet集合"+num.tailSet(50));} } [-13, -5, 0, 40, 45, 78, 99]輸出集合的第一個元素-13
輸出集合的最后一個元素99
輸出小于50的最大的元素45
輸出大于50的最小元素78
輸出10到80之間的元素[40, 45, 78]
輸出小于50的元素[-13, -5, 0, 40, 45]
輸出大于50的元素的treeSet集合[78, 99]
TreeSet并不是根據元素的插入順序來排序的,而是根據元素實際值的大小來進行排序的。
與HashSet集合采用hash算法來決定元素的存儲位置不同,TreeSet采用紅黑樹的數據結構來存儲集合數據。TreeSet支持兩種排序方法:自然排序和定制排序,默認為自然排序。
1.自然排序
TreeSet會調用集合元素的compareTo(Object obj)方法來比較元素之間的大小關系。然后將集合元素按照升序排列,這種方式就是自然排序。
Java提供了一個Comparable接口,該接口里定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現該接口的類必須實現該方法,實現了該接口的類的對象就可以比較大小。當一個對象調用該方法與另一個對象進行比較是。例如obj1.compareTo(obj2),如果改方法返回0,則表明這兩個對象相等,如果該方法返回一個正整數,則表明obj1大于obj2;如果該方法返回一個負整數,則表明obj1小于obj2.
Java的一些常用類已經實現了Comparable接口,并提供了比較大小的標準。下面是實現了Comparable接口的常用類。
BigDecimal,Character,Boolean,String,Date,Time.
如果把一個自定義的類的對象添加到treeSet中,則該對象對應的類必須實現Comparable接口,否則程序將會拋出異常ClassCastException.
大部分類在實現compareTo(Object obj)方法時,都需要將被比較對象obj強制類型轉換成相同類型,因為只有相同類的兩個實例才會比較大小。
如果向TreeSet中添加的對象是程序員自定義的類的對象,則可以向TreeSet中添加多種類型的對象,前提是用戶自定義的類實現了Comparable接口,且實現compareTo(Object obj)方法沒有進行強制類型轉換。但是當試圖取出TreeSet集合元素時,不同類型的元素依然會發(fā)生ClassCastException異常。
總結:如果希望TreeSet能正常運作,TreeSet只能添加同一種類型的對象。
當一個對象加入TreeSet集合中時,TreeSet調用該對象的compareTo(Object obj)方法與容器中的其他對象比較,然后根據紅黑樹結構找到它的存儲位置。如果兩個對象通過compareTo(Object obj)方法比較相等,新對象將無法添加到TreeSet集合中。
對于TreeSet集合而言,它判斷兩個對象是否相等的唯一標準是:兩個對象通過compareTo(Object obj)方法比較是否返回0,如果返回0,則認為相等,否則就認為它們不相等。
?
package com.j1803.setTest;import java.util.TreeSet;/*** @author zhou_oyster**/ class Person implements Comparable{private int age;@Overridepublic boolean equals(Object obj) {return true;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic int compareTo(Object arg0) {return 1;}public Person(int age) {super();this.age = age;} } public class TreeSetTest1 {public static void main(String[] args) {TreeSet book=new TreeSet();Person person=new Person(45);book.add(person);System.out.println(book.add(person));//輸出true,添加成功System.out.println(book);//顯示所有的元素//取出book集合中的第一個元素并修改年齡((Person)book.first()).setAge(12);//查看第一個元素的年齡和最后一個元素的年齡System.out.println(((Person)book.first()).getAge()+"===================="+((Person)book.last()).getAge()); }} true [com.j1803.setTest.Person@7852e922, com.j1803.setTest.Person@7852e922] 12====================12可以看到雖然修改Comparable的compareTo()方法,誤讓程序以為person和他本身不相等,從而可以添加成功,集合中保存對象的引用指的是同一個對象,所以修改了第一個age,后面的age也修改了。
故:當需要把一個對象放入TreeSet中,重寫該對象對應類的equals()方法時,應保證該方法與compareTo(Object obj)方法有一致結果,其規(guī)則是:如果兩個對象通過equals()方法比較返回true,這兩個對象通過compareTo()方法應該返回0.
反之如果compareTo(Object obj)返回0而equals()返回false,則會與Set規(guī)則產生沖突。
如果向TreeSet中添加了可變對象,并且后面的程序修改了該可變對象的實例變量,將導致它與其他對象的大小順序發(fā)生了改變,但TreeSet不會再次調整它們的順序,甚至可能導致TreeSet中保存的這兩個對象通過compareTo(Object obj)方法比較返回0.
package com.j1803.setTest; import java.util.TreeSet; public class Book implements Comparable{private int price;public Book(int price) {super();this.price = price;}@Overridepublic String toString() {return "Book [price=" + price + "]";}public int getPrice() {return price;}public void setPrice(int price) {this.price = price;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Book other = (Book) obj;if (price != other.price)return false;return true;}@Overridepublic int compareTo(Object obj) {Book book=(Book)obj;return this.price>book.getPrice()?1:this.price<book.getPrice()?-1:0;}public static void main(String[] args) {TreeSet set=new TreeSet();set.add(new Book(12));set.add(new Book(10));set.add(new Book(-10));set.add(new Book(-5));set.add(new Book(5));set.add(new Book(8));//打印set集合 System.out.println(set);//修改第一個元素Book book1=(Book)set.first();book1.setPrice(13);//修改最后一個元素,使其與第二個元素的price相同Book book2=(Book)set.last();book2.setPrice(-5);//打印,可以看到無序且有重復元素 System.out.println(set);//刪除實例變量被改變的元素,刪除失敗。System.out.println(set.remove(new Book(13)));//打印 System.out.println(set);//刪除實例變量沒有被改變的元素,刪除成功。 System.out.println(set.remove(new Book(10)));//打印 System.out.println(set); }} [Book [price=-10], Book [price=-5], Book [price=5], Book [price=8], Book [price=10], Book [price=12]] [Book [price=13], Book [price=-5], Book [price=5], Book [price=8], Book [price=10], Book [price=-5]] false [Book [price=13], Book [price=-5], Book [price=5], Book [price=8], Book [price=10], Book [price=-5]] true [Book [price=13], Book [price=-5], Book [price=5], Book [price=8], Book [price=-5]]可以刪除沒有被修改實例變量,且不與其他修改實例變量的對象重復的對象。
當執(zhí)行了紅色代碼后TreeSet會對集合中的元素重新索引(不是重新排序),接下來可以刪除TreeSet所有元素,推薦不要修改放入HashSet和TreeSet集合中元素的關鍵實例變量。
//刪除元素 System.out.println(set.remove(new Book(-5))); System.out.println(set); [Book [price=13], Book [price=-5], Book [price=5], Book [price=8], Book [price=-5]] true [Book [price=13], Book [price=5], Book [price=8], Book [price=-5]]2.定制排序
TreeSet的自然排序是根據元素的大小,TreeSet將它們以升序排列。如果需要實現定制排序,例如以降序排列。則可以通過Comparator接口的幫助。,該接口里包含了一個int compare(T o1,T o2)方法。該方法用于比較o1和o2的大小:如果該方法中返回正整數,則表明o1大于o2;如果該方法返回0,則表明o1等于o2;如果該方法返回負整數,則表明o1大于o2.
如果需要實現定制排序,則需要在創(chuàng)建TreeSet集合對象時,提供一個Comparator對象與該TreeSet集合關聯,由該Comparator對象負責集合元素的排序邏輯。由于Comparator是一個函數式接口,因此可用Lambda表達式來代替Comparator對象。
四? EnumSet類
EnumSet是一個專為枚舉類設計的集合類,EnumSet中的所有元素都必須是指定枚舉類型的枚舉值。該枚舉類型在創(chuàng)建EnumSet時顯示或隱式地指定。EnumSet的集合元素也是有序的,EnumSet以枚舉值在Enum類內的定義順序來決定集合元素的順序。
EnumSet在內部以位向量的形式存儲,這種存儲方式非常緊湊,高效,因此EnumSet對象占用內存很小,而且運行效率很好。尤其是進行批量操作(如調用containsAll()和remainAll()方法)時,如果其參數也是EnumSet集合,則該批量操作的執(zhí)行速度也非???。
EnumSet集合不允許加入null元素,如果試圖插入null元素,EnumSet將拋出NullPointException異常。如果只是想判斷EnumSet是否包含null元素或者試圖刪除null元素都不會拋出異常,只是刪除操作將返回false,因為沒有任何null元素被刪除。
EnumSet類沒有暴露任何構造器來創(chuàng)建該類的實例,程序應該通過它提供的類方法來創(chuàng)建EnumSet對象。EnumSet類它提供了如下常用的類方法來創(chuàng)建EnumSet對象。
EnumSet allOf(Class elementType):創(chuàng)建一個包含指定枚舉類里所有枚舉值的EnumSet集合.
EnumSet complementOf(Enumset s):創(chuàng)建一個其元素類型與指定EnumSet里元素類型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的,此枚舉類剩下的枚舉值(也就是新EnumSet集 ? ? ? 合和原來EnumSet集合的集合元素加起來都是該枚舉類的所有枚舉值)。
EnumSet copyOf(Collection c):使用一個普通集合來創(chuàng)建EnumSet集合。
EnumSet copyOf(EnumSet s):創(chuàng)建一個與指定EnumSet具有相同元素類型,相同集合元素的EnumSet集合。
EnumSet noneOf(Class elementType):創(chuàng)建一個元素類型為指定枚舉類型的空EnumSet.
EnumSet of(E first,E...rest):創(chuàng)建一個包含一個或多個枚舉值的EnumSet集合,傳入的多個枚舉值必須屬于同一個枚舉類。
EnumSet range(E from,E to):創(chuàng)建一個包含從from枚舉值到to枚舉值范圍內所有枚舉值的EnumSet集合。
?
package com.j1803.EnumSetTest1; import java.util.EnumSet; enum Season{SPRING,SUMMER,FALL,WINTER } public class EnumSetTest {public static void main(String[] args) {//創(chuàng)建一個EnumSet集合,集合元素就是Season枚舉類的全部枚舉值。EnumSet esl= EnumSet.allOf(Season.class);//輸出[SPRING,SUMMER,FALL,WINTER] System.out.println(esl);//創(chuàng)建一個EnumSet空集合,指定集合元素是Season類的枚舉值。EnumSet es2=EnumSet.noneOf(Season.class);//輸出[] System.out.println(es2);//手動添加元素。 es2.add(Season.FALL);es2.add(Season.SPRING);//輸出[FALL,SPRING] System.out.println(es2);//以指定枚舉值創(chuàng)建EnumSet集合EnumSet es3=EnumSet.of(Season.SUMMER,Season.FALL,Season.WINTER);//輸出[SUMMER,FALL,WINTER] System.out.println(es3);EnumSet es4=EnumSet.of(Season.SUMMER,Season.WINTER);//新創(chuàng)建的EnumSet集合元素和es4集合元素有相同的類型//es5集合元素+es4集合元素=Season枚舉類的全部枚舉值EnumSet es5= EnumSet.complementOf(es4);System.out.println(es5);} } [SPRING, SUMMER, FALL, WINTER] [] [SPRING, FALL] [SUMMER, FALL, WINTER] [SPRING, FALL]要求復制另一個Collection集合中的所有元素到新創(chuàng)建的
package com.j1803.EnumSetTest1; import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; enum Season{SPRING,SUMMER,FALL,WINTER } public class EnumSetTest {public static void main(String[] args) {Collection c1=new HashSet();c1.add(Season.SUMMER);c1.add(Season.SPRING);c1.add(Season.FALL);//復制Collection集合中的所有元素來創(chuàng)建EnumSet集合EnumSet enumSet= EnumSet.copyOf(c1);//輸出[SUMMER,SPRING,FALL]System.out.println(enumSet);//運行報ClassCastException錯誤//enumSet.add("PHP");//enumSet.add("C++");enumSet.add(Season.WINTER);System.out.println(enumSet);} } [SPRING, SUMMER, FALL] [SPRING, SUMMER, FALL, WINTER]?五? 各Set實現類的性能分析
HashSet與TreeSet:HashSet的性能是比TreeSet要好,因為TreeSet需要額外的紅黑樹算法來維護集合元素的次序,當需要一個注重保持排序的Set時,才使用TreeSet。
EnumSet是所有Set實現類中性能最好的,但它只能保持同一個枚舉類的枚舉值作為集合元素。
HashSet,TreeSet和EnumSet都是線程不安全的。如果有多個線程同時訪問一個Set集合,并且有超過一個線程修改了該Set集合,則必須手動保證該Set集合的同步性。通??梢酝ㄟ^Collection工具類的synchronizedSortedSet方法來"包裝"該Set集合。在創(chuàng)建時進行,以防止對Set集合的意外非同步訪問。
SortedSet sortedSet= Collections.synchronizedSortedSet(new TreeSet());?
?
?
?
轉載于:https://www.cnblogs.com/shadow-shine/p/9710892.html
總結
以上是生活随笔為你收集整理的java集合(4)-Set集合的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HP-UX平台安装Oracle11gR2
- 下一篇: 【题解】Luogu P2783 有机化学