標籤:set hashset treeset linkedhash
上篇部落格講了Collection介面的一些基本操作,這篇部落客要介紹Collection介面的子介面Set。
Set是一種無序的集合,其基本操作和Collection介面是差不多的,主要的不同點在於Set中不能重複元素而Collection集合是可以的。對於Set集合我們主要關心它的HashSet,TreeSet兩個實作類別。
一.HashSet
HashSet是Set介面的典型實現,大多數時候使用Set集合時就是使用這個類;HashSet通過hash演算法確定元素的儲存位置。值得注意的是HashSet中可以儲存值為null的元素。
那麼HashSet是怎樣工作的呢?我們每傳入一個元素,HashSet就調用該對象的HashCode()方法計算得到該對象的HashCode值,然後就根據這個值決定該元素在HashSet中的儲存位置。如果兩個對象通過equals()比較相等,但是計算出的HashCode值不相同,那麼HashSet就將他們儲存到不同的位置上去;如果兩個對象的HashCode值相等,而通過equals()方法比較不相等,那麼HashSet就將他們在同一個位置上以鏈表的形式儲存。
綜上我們可以知道HashSet判斷兩個對象相等的標準是:兩個對象的HashCode值相同,並且兩個對象通過equals()方法比較返回true。所以這兩個方法對HashSet來說是十分重要的,我們需要保證當兩個對象的HashCode值一樣時,其通過equals()方法比較也會返回true。下面的程式中A,B,C類分別重寫了自己的HashCode()和equals()方法。
public class A { ///重寫A類的equals()方法,使其總是返回true public boolean equals(Object obj){ return true; }}public class B { ///重寫B類的HashCode()方法,使其總是返回2 public int hashCode(){ return 2; }}public class C { ///重寫C類的equals()方法,使其總是返回true public boolean equals(Object obj){ return true; } ///重寫C類的HashCode()方法,使其總是返回3 public int hashCode(){ return 3; }}public class HashCodeTest { public static void main(String[] args){ HashSet st = new HashSet(); //依次添加兩個A,B,C類對象 st.add(new A()); st.add(new A()); st.add(new B()); st.add(new B()); st.add(new C()); st.add(new C()); //可以看到只輸出了一個C類,而A,B類都輸出了兩個 System.out.println(st); }}
HashSet有一個叫LinkedHashSet的子類,LinkedHashSet的基本原理和HashSet是一樣的,只是LinkedHashSet內部通過一個鏈表來維護插入元素的相對順序,這樣使得看起來元素是以插入的順序儲存的;當我們遍曆LinkedHashSet時就可以將元素以其輸入順序輸出了。
package lkl;import java.util.LinkedHashSet;public class LinkedHashSetTest { public static void main(String[] args){ LinkedHashSet lt = new LinkedHashSet(); lt.add("java"); lt.add("c"); lt.add("c++"); lt.add("shell"); ///從下面兩次輸出可以看出,元素總是以輸入順序輸出 System.out.println(lt); lt.remove("java"); lt.add("java"); ///從新添加後java被放到最後 System.out.println(lt); }}
二.TreeSet
TreeSet是Set的另一個重要的子類。與HashSet通過hash的方式確定元素的儲存位置不同,TreeSet內部通過一棵紅/黑樹狀結構來維護元素的相對次序;由於TreeSet中元素的有序性,相對HashSet,還提供了幾個額外的方法,如下面的代碼所示:
package lkl;import java.util.TreeSet;public class TreeSetTest {public static void main(String[] args){ TreeSet ts = new TreeSet(); ts.add(1); ts.add(-2); ts.add(7); ts.add(4); ///從下面的輸出可以看出TreeSet是有序的 System.out.println(ts); ///Comparator comparator() ///如果TreeSet採用了定製排序,則返回定製排序的Comparator ///如果採用了自然排序,則返回null System.out.println(ts.comparator()); ///Object first():返回集合中第一個元素 ///Object last():返回集合中最後一個元素 System.out.println("集合中第一個元素為: "+ts.first()); System.out.println("集合中最後一個元素為: "+ts.last()); ///Object lower(Object e):返回集合中第一個小於e的元素,e不一定在集合中 ///Object higher(Object e):返回集合中第一個大於e的元素 ///如果沒有元素滿足的話,返回null System.out.println("集合中第一個小於5的元素是: "+ts.lower(5)); System.out.println("集合中第一個大於5的元素是: "+ts.higher(5)); ///SortedSet subSet(Object e1,Object e2):返回Set從e1-e2之間的子集 System.out.println(ts.subSet(1,5)); ///SortSet headSet(Object e):返回集合中小於e的元素組成的子集 System.out.println(ts.headSet(6)); ///SortSet tailSet(Object e):返回集合中大於等於e元素組成的子集 System.out.println(ts.tailSet(1));}}
因為TreeSet是有序的,所以插入一個元素時總是要比較大小;這裡進行大小比較時總是調用對象obj1.compareTo(Object obj2)方法(返回正整數表示obj1大於obj2,返回負整數時表示obj1小於obj2,返回0時表示相等),只有兩個對象通過compareTo()比較返回0時,才認為兩個對象相等。這導致一個問題,不同的對象不能被同時插入到TreeSet中,因為不同的對象不能通過comparaTo()方法進行比較;當我們將不同的對象插入到TreeSet中時會引發異常。
如果我們想自己定義排序的規則,我們可以藉助Comparator介面。該介面中包含了一個int compare(T t1,T t2)的方法,該方法在t1>t2時返回正整數,在t1
package lkl;import java.util.TreeSet;import java.util.Comparator;public class TreeSetComparatorTest {///TreeSet原本是從從小到大輸出的,現在自己定製排序後///可以實現從大到小輸出 public static void main(String[] args){ TreeSet ts = new TreeSet(new Comparator(){ public int compare(Object o1,Object o2){ int m1= (int)o1; int m2= (int)o2; if(m1==m2) return 0; if(m1>m2) return -1; return 1; } }); ts.add(1); ts.add(2); ts.add(-1); System.out.println(ts); }}
最後要強調一句,如果我們向set中添加了可變的對象,然後又改變了該對象中的File,那麼可能造成該對象在set中”不可見”的情況;如果我們向set中加入了可變的對象,那麼我們不要在後面再改變其中File的值。
Java集合學習之Collection(2)