上一篇文章介紹了Set集合的通用知識(shí)。Set集合中包含了三個(gè)比較重要的實(shí)現(xiàn)類(lèi):HashSet、TreeSet和EnumSet。本篇文章將重點(diǎn)介紹這三個(gè)類(lèi)。
一亥啦、HashSet類(lèi)
HashSet簡(jiǎn)介
HashSet是Set接口的典型實(shí)現(xiàn),實(shí)現(xiàn)了Set接口中的所有方法练链,并沒(méi)有添加額外的方法翔脱,大多數(shù)時(shí)候使用Set集合時(shí)就是使用這個(gè)實(shí)現(xiàn)類(lèi)。HashSet按Hash算法來(lái)存儲(chǔ)集合中的元素兑宇。因此具有很好的存取和查找性能碍侦。
HashSet特點(diǎn)
1.不能保證元素的排列順序,順序可能與添加順序不同隶糕,順序也有可能發(fā)生變化瓷产。
2.HashSet不是同步的,如果多個(gè)線程同時(shí)訪問(wèn)一個(gè)HashSet枚驻,則必須通過(guò)代碼來(lái)保證其同步濒旦。
3.集合元素值可以是null。
除此之外再登,HashSet判斷兩個(gè)元素是否相等的標(biāo)準(zhǔn)也是其一大特點(diǎn)尔邓。HashSet集合判斷兩個(gè)元素相等的標(biāo)準(zhǔn)是兩個(gè)對(duì)象通過(guò)equals()方法比較相等晾剖,并且兩個(gè)對(duì)象的hashCode()方法返回值也相等。
寫(xiě)到這里梯嗽,我們就要介紹下equals()和hashCode()方法了齿尽。
equals()和hashCode()
equals()
equals() 的作用是** 用來(lái)判斷兩個(gè)對(duì)象是否相等。**
equals() 定義在JDK的Object.java中灯节。通過(guò)判斷兩個(gè)對(duì)象的地址是否相等(即循头,是否是同一個(gè)對(duì)象)來(lái)區(qū)分它們是否相等。源碼如下:
public boolean equals(Object obj) {
return (this == obj);
}
既然Object.java中定義了equals()方法炎疆,這就意味著所有的Java類(lèi)都實(shí)現(xiàn)了equals()方法卡骂,所有的類(lèi)都可以通過(guò)equals()去比較兩個(gè)對(duì)象是否相等。 但是形入,使用默認(rèn)的“equals()”方法全跨,等價(jià)于“==”方法。我們也可以在Object的子類(lèi)中重寫(xiě)此方法亿遂,自定義“equals()”方法浓若,在其中定義自己的判斷邏輯,如果滿(mǎn)足則返回true崩掘,不滿(mǎn)足則返回false七嫌。下面我們自定義一個(gè)類(lèi) Person,并認(rèn)為年齡,身高相等的兩個(gè)Person對(duì)象苞慢,equals()方法比較結(jié)果相等。
public class Person {
public int age;
public int height;
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (height != other.height)
return false;
return true;
}
}
public class EqualTest {
public static void main(String[] args){
Person p1 = new Person();
Person p2 =new Person();
System.out.println(p1.equals(p2));
}
}
輸出結(jié)果:
true
下面根據(jù)“類(lèi)是否覆蓋equals()方法”英妓,將它分為2類(lèi)挽放。
(01) 若某個(gè)類(lèi)沒(méi)有覆蓋equals()方法,當(dāng)它的通過(guò)equals()比較兩個(gè)對(duì)象時(shí)蔓纠,實(shí)際上是比較兩個(gè)對(duì)象是不是同一個(gè)對(duì)象辑畦。這時(shí),等價(jià)于通過(guò)“==”去比較這兩個(gè)對(duì)象腿倚,即兩個(gè)對(duì)象的內(nèi)存地址是否相同纯出。
(02) 我們可以覆蓋類(lèi)的equals()方法,來(lái)讓equals()通過(guò)其它方式比較兩個(gè)對(duì)象是否相等敷燎。通常的做法是:若兩個(gè)對(duì)象的內(nèi)容相等暂筝,則equals()方法返回true;否則硬贯,返回fasle焕襟。
hashCode()
hashCode() 的作用是獲取哈希碼,也稱(chēng)為散列碼饭豹;它實(shí)際上是返回一個(gè)int整數(shù)鸵赖。這個(gè)哈希碼的作用是確定該對(duì)象在哈希表中的索引位置务漩。
hashCode() 定義在JDK的Object.java中,這就意味著Java中的任何類(lèi)都包含有hashCode() 函數(shù)它褪。雖然饵骨,每個(gè)Java類(lèi)都包含hashCode() 函數(shù)。但是茫打,僅僅當(dāng)創(chuàng)建某個(gè)“類(lèi)"的散列表時(shí)宏悦,該類(lèi)的hashCode() 才有用。更通俗地說(shuō)就是創(chuàng)建包含該類(lèi)的HashMap包吝,Hashtable饼煞,HashSet集合時(shí),hashCode() 才有用诗越。因?yàn)镠ashMap砖瞧,Hashtable,HashSet就是散列表集合嚷狞。
在散列表中块促,hashCode()作用是:確定該類(lèi)的每一個(gè)對(duì)象在散列表中的位置;其它情況下類(lèi)的hashCode() 沒(méi)有作用床未。在散列表中hashCode() 的作用是獲取對(duì)象的散列碼竭翠,進(jìn)而確定該對(duì)象在散列表中的位置。
hashCode()也分兩種情況薇搁。一種是Object類(lèi)中默認(rèn)的方法斋扰,另一種是在子類(lèi)中重寫(xiě)的方法。
(01) 若某個(gè)類(lèi)沒(méi)有覆蓋hashCode()方法啃洋,當(dāng)它的通過(guò)hashCode()比較兩個(gè)對(duì)象時(shí)传货,實(shí)際上是比較兩個(gè)對(duì)象是不是同一個(gè)對(duì)象。這時(shí)宏娄,等價(jià)于通過(guò)“==”去比較這兩個(gè)對(duì)象问裕,即兩個(gè)對(duì)象的內(nèi)存地址是否相同。
(02) 我們可以覆蓋類(lèi)的hashCode()方法孵坚,來(lái)讓hashCode()通過(guò)其它方式比較兩個(gè)對(duì)象是否相等粮宛。通常的做法是:若兩個(gè)對(duì)象的內(nèi)容相等,則hashCode()方法返回true卖宠;否則巍杈,返回fasle。
通過(guò)對(duì)以上兩個(gè)方法的了解逗堵。我們可以接下來(lái)學(xué)習(xí)HashSet集合中如何判斷兩個(gè)元素是否相等秉氧?
HashSet中判斷集合元素相等
兩個(gè)對(duì)象比較 具體分為如下四個(gè)情況:
1.如果有兩個(gè)元素通過(guò)equal()方法比較返回false,但它們的hashCode()方法返回不相等蜒秤,HashSet將會(huì)把它們存儲(chǔ)在不同的位置汁咏。
2.如果有兩個(gè)元素通過(guò)equal()方法比較返回true亚斋,但它們的hashCode()方法返回不相等,HashSet將會(huì)把它們存儲(chǔ)在不同的位置攘滩。
3.如果兩個(gè)對(duì)象通過(guò)equals()方法比較不相等帅刊,hashCode()方法比較相等,HashSet將會(huì)把它們存儲(chǔ)在相同的位置漂问,在這個(gè)位置以鏈表式結(jié)構(gòu)來(lái)保存多個(gè)對(duì)象赖瞒。這是因?yàn)楫?dāng)向HashSet集合中存入一個(gè)元素時(shí),HashSet會(huì)調(diào)用對(duì)象的hashCode()方法來(lái)得到對(duì)象的hashCode值蚤假,然后根據(jù)該hashCode值來(lái)決定該對(duì)象存儲(chǔ)在HashSet中存儲(chǔ)位置栏饮。
4.如果有兩個(gè)元素通過(guò)equal()方法比較返回true,但它們的hashCode()方法返回true磷仰,HashSet將不予添加袍嬉。
HashSet判斷兩個(gè)元素相等的標(biāo)準(zhǔn):兩個(gè)對(duì)象通過(guò)equals()方法比較相等,并且兩個(gè)對(duì)象的hashCode()方法返回值也相等灶平。
注意:HashSet是根據(jù)元素的hashCode值來(lái)快速定位的伺通,如果HashSet中兩個(gè)以上的元素具有相同的hashCode值,將會(huì)導(dǎo)致性能下降逢享。所以如果重寫(xiě)類(lèi)的equals()方法和hashCode()方法時(shí)罐监,應(yīng)盡量保證兩個(gè)對(duì)象通過(guò)hashCode()方法返回值相等時(shí),通過(guò)equals()方法比較返回true瞒爬。
LinkedHashSet類(lèi)
LinkedHashSet是HashSet對(duì)的子類(lèi)弓柱,也是根據(jù)元素的hashCode值來(lái)決定元素的存儲(chǔ)位置,同時(shí)使用鏈表維護(hù)元素的次序疮鲫,使得元素是以插入的順序來(lái)保存的吆你。當(dāng)遍歷LinkedHashSet集合里的元素時(shí),LinkedHashSet將會(huì)按元素的添加順序來(lái)訪問(wèn)集合里的元素俊犯。但是由于要維護(hù)元素的插入順序,在性能上略低與HashSet伤哺,但在迭代訪問(wèn)Set里的全部元素時(shí)有很好的性能燕侠。
注意:LinkedHashSet依然不允許元素重復(fù),判斷重復(fù)標(biāo)準(zhǔn)與HashSet一致立莉。
補(bǔ)充:HashSet的實(shí)質(zhì)是一個(gè)HashMap绢彤。HashSet的所有集合元素,構(gòu)成了HashMap的key蜓耻,其value為一個(gè)靜態(tài)Object對(duì)象茫舶。因此HashSet的所有性質(zhì),HashMap的key所構(gòu)成的集合都具備刹淌∪氖希可以參考后續(xù)文章中HashMap的相關(guān)內(nèi)容進(jìn)行比對(duì)讥耗。
二、TreeSet類(lèi)
TreeSet簡(jiǎn)介
TreeSet是SortedSet接口的實(shí)現(xiàn)類(lèi)疹启,正如SortedSet名字所暗示的古程,TreeSet可以確保集合元素處于排序狀態(tài)。此外喊崖,TreeSet還提供了幾個(gè)額外的方法挣磨。
TreeSet的方法
comparator():返回對(duì)此 set 中的元素進(jìn)行排序的比較器;如果此 set 使用其元素的自然順序荤懂,則返回null茁裙。
first():返回此 set 中當(dāng)前第一個(gè)(最低)元素。
last(): 返回此 set 中當(dāng)前最后一個(gè)(最高)元素节仿。
lower(E e):返回此 set 中嚴(yán)格小于給定元素的最大元素晤锥;如果不存在這樣的元素,則返回 null粟耻。
higher(E e):返回此 set 中嚴(yán)格大于給定元素的最小元素查近;如果不存在這樣的元素,則返回 null挤忙。
subSet(E fromElement, E toElement):返回此 set 的部分視圖霜威,其元素從 fromElement(包括)到 toElement(不包括)。
headSet(E toElement):返回此 set 的部分視圖册烈,其元素小于toElement戈泼。
tailSet(E fromElement):返回此 set 的部分視圖,其元素大于等于 fromElement赏僧。
TreeSet的排序方式
TreeSet中所謂的有序大猛,不同于之前所講的插入順序,而是通過(guò)集合中元素屬性進(jìn)行排序方式來(lái)實(shí)現(xiàn)的淀零。
TreeSet支持兩種排序方法:自然排序和定制排序挽绩。在默認(rèn)情況下,TreeSet采用自然排序驾中。
1.自然排序
在講自然排序之前唉堪,要先講一下Comparable接口。
Java提供了一個(gè)Comparable接口肩民,該接口里定義了一個(gè)compareTo(Object obj)方法,該方法返回一個(gè)整數(shù)值唠亚,實(shí)現(xiàn)該接口的類(lèi)必須實(shí)現(xiàn)該方法,實(shí)現(xiàn)了該接口的類(lèi)的對(duì)象就可以比較大小了持痰。當(dāng)一個(gè)對(duì)象調(diào)用該方法與另一個(gè)對(duì)象比較時(shí)灶搜,例如obj1.compareTo(obj2),如果該方法返回0,則表明兩個(gè)對(duì)象相等;如果該方法返回一個(gè)整數(shù)割卖,則表明obj1大于obj2;如果該方法返回一個(gè)負(fù)整數(shù)前酿,則表明oj1小于obj2。
TreeSet會(huì)調(diào)用集合中元素所屬類(lèi)的compareTo(Object obj)方法來(lái)比較元素之間的大小關(guān)系究珊,然后將集合元素按升序排列薪者,即把通過(guò)compareTo(Object obj)方法比較后比較大的的往后排。這種方式就是自然排序剿涮。
Java的一些常用類(lèi)已經(jīng)實(shí)現(xiàn)了Comparable接口言津,并提供了比較大小的標(biāo)準(zhǔn)。例如取试,String按字符串的UNICODE值進(jìn)行比較悬槽,Integer等所有數(shù)值類(lèi)型對(duì)應(yīng)的包裝類(lèi)按它們的數(shù)值大小進(jìn)行比較。
除了這些已經(jīng)實(shí)現(xiàn)Comparable接口類(lèi)之外瞬浓,如果試圖把一個(gè)對(duì)象添加到TreeSet時(shí)初婆,則該對(duì)象的類(lèi)必須實(shí)現(xiàn)Comparable接口,否則就會(huì)出現(xiàn)異常猿棉。
注意:TreeSet中只能添加同一種類(lèi)型的對(duì)象磅叛,否則無(wú)法比較,會(huì)出現(xiàn)異常萨赁。
TreeSet中判斷集合元素相等
對(duì)于TreeSet集合而言弊琴,判斷兩個(gè)對(duì)象是否相等的唯一標(biāo)準(zhǔn)是:兩個(gè)對(duì)象通過(guò)compareTo(Object obj)方法比較是否返回0——如果通過(guò)compareTo(Object obj)方法比較返回0,TreeSet則會(huì)認(rèn)為它們相等杖爽,不予添加入集合內(nèi)敲董;否則就認(rèn)為它們不相等,添加到集合內(nèi)慰安。
TreeSet是根據(jù)紅黑樹(shù)結(jié)構(gòu)找到集合元素的存儲(chǔ)位置腋寨。
2.定制排序
TreeSet的自然排序是根據(jù)集合元素中compareTo(Object obj)比較的大小,以升序排列化焕。而定制排序是通過(guò)Comparator接口的幫助萄窜。該接口包含一個(gè)int compare(T o1,T o2)方法,該方法用于比較o1,o2的大腥鼋啊:如果該方法返回正整數(shù)脂倦,則表明o1大于o2;如果該方法返回0元莫,則表明o1等于o2;如果該方法返回負(fù)整數(shù),則表明o1小于o2蝶押。
如果要實(shí)現(xiàn)定制排序踱蠢,則需要在創(chuàng)建TreeSet時(shí),調(diào)用一個(gè)帶參構(gòu)造器,傳入Comparator對(duì)象茎截。并有該Comparator對(duì)象負(fù)責(zé)集合元素的排序邏輯苇侵,集合元素可以不必實(shí)現(xiàn)Comparable接口。下面具體演示一下這種用法:
public static void main(String[] args){
Person p1 = new Person();
p1.age =20;
Person p2 =new Person();
p2.age = 30;
Comparator<Person> comparator = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
//年齡越小的排在越后面
if(o1.age<o2.age){
return 1;
}else if(o1.age>o2.age){
return -1;
}else{
return 0;
}
}
};
TreeSet<Person> set = new TreeSet<Person>(comparator);
set.add(p1);
set.add(p2);
System.out.println(set);
}
[Person[age=30], Person[age=20]]
總結(jié):無(wú)論使用自然排序還是定制排序企锌,都可以通過(guò)自定義比較邏輯實(shí)現(xiàn)各種各樣的排序方式榆浓。
注意:如果向TreeSet中添加了一個(gè)可變對(duì)象后,并且后面程序修改了該可變對(duì)象的實(shí)例變量撕攒,這將導(dǎo)致它與其他對(duì)象的大小順序發(fā)生了改變陡鹃,但TreeSet不會(huì)再次調(diào)整它們。下面程序演示這一現(xiàn)象:
TreeSet<Person> set = new TreeSet<Person>();
Person p1 = new Person();
p1.setAge(10);
Person p2 =new Person();
p2.setAge(30);
Person p3 =new Person();
p3.setAge(40);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("初始年齡排序");
System.out.println(set);
//p1的年齡修改成50 最大
p1.age = 60;
System.out.println("修改p1年齡后集合排序");
System.out.println(set);
p2.age = 40;
System.out.println("修改p2年齡后集合排序");
System.out.println(set);
Person p4 = new Person();
其中Person實(shí)現(xiàn)Comparable接口抖坪,將Person對(duì)象按照年齡從小到大升序排列萍鲸。
輸出結(jié)果:
初始年齡排序
[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]]
可以看到并沒(méi)有發(fā)生變化,而且如果修改后進(jìn)行元素刪除操作可能會(huì)不成功擦俐,具體比較復(fù)雜脊阴。總之蚯瞧,推薦不要修改放入TreeSet集合中元素的關(guān)鍵實(shí)例變量嘿期。
補(bǔ)充:TreeSet也是非線程安全的。
三埋合、EnumSet類(lèi)
EnumSet簡(jiǎn)介
EnumSet是一個(gè)專(zhuān)為枚舉類(lèi)設(shè)計(jì)的集合類(lèi)备徐,EnumSet中的所有元素都必須是指定枚舉類(lèi)型的枚舉值,該枚舉類(lèi)型在創(chuàng)建EnumSet時(shí)顯示或隱式地指定饥悴。EnumSet的集合元素也是有序的坦喘,EnumSet以枚舉值在EnumSet類(lèi)內(nèi)的定義順序來(lái)決定集合元素的順序。
EnumSet特點(diǎn)
1.EnumSet集合不允許加入null元素西设。EnumSet中的所有元素都必須是指定枚舉類(lèi)型的枚舉值瓣铣。
2.EnumSet類(lèi)沒(méi)有暴露任何構(gòu)造器來(lái)創(chuàng)建該類(lèi)的實(shí)例,程序應(yīng)該通過(guò)它提供的類(lèi)方法來(lái)創(chuàng)建EnumSet對(duì)象贷揽。
EnumSet沒(méi)有其他額外增加的方法棠笑,只是增加了一些創(chuàng)建EnumSet對(duì)象的方法。
EnumSet創(chuàng)建對(duì)象的方法
補(bǔ)充:EnumSet 也是非線程安全的禽绪。
四蓖救、HashSet、TreeSet和EnumSet的性能對(duì)比
EnumSet性能>HashSet性能>LinkedHashSet>TreeSet性能
EnumSet內(nèi)部以位向量的形式存儲(chǔ)印屁,結(jié)構(gòu)緊湊循捺、高效潜索,且只存儲(chǔ)枚舉類(lèi)的枚舉值奕翔,所以最高效。HashSet以hash算法進(jìn)行位置存儲(chǔ)拗盒,特別適合用于添加、查詢(xún)操作恰力。LinkedHashSet由于要維護(hù)鏈表叉谜,性能比HashSet差點(diǎn),但是有了鏈表踩萎,LinkedHashSet更適合于插入停局、刪除以及遍歷操作。而TreeSet需要額外的紅黑樹(shù)算法來(lái)維護(hù)集合的次序香府,性能最次董栽。
但是具體使用要考慮具體的使用場(chǎng)景。
當(dāng)需要一個(gè)特定排序的集合時(shí)回还,使用TreeSet集合裆泳。
當(dāng)需要保存枚舉類(lèi)的枚舉值時(shí),使用EnumSet集合柠硕。
當(dāng)經(jīng)常使用添加工禾、查詢(xún)操作時(shí),使用HashSet蝗柔。
當(dāng)經(jīng)常插入排序或使用刪除闻葵、插入及遍歷操作時(shí),使用LinkedHashSet癣丧。
作者:Ruheng
鏈接:http://www.reibang.com/p/9081017a2d67
來(lái)源:簡(jiǎn)書(shū)
著作權(quán)歸作者所有槽畔。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處胁编。