標籤:false lis 多個 鏈表 冗餘 操作 等於 原理 width
java集合概述之Set
Abstract
- Java的集合主要有
Set、List、Queue和Map四種體系。 這四種體系都是介面不能直接用,但是在這四種體系下包含了很多的實作類別是可以直接使用的。
- 集合類和定長數組的區別主要在於,定長數組不僅可以儲存基礎資料型別 (Elementary Data Type)還有對象,但是集合類只能儲存物件。這裡的對象是指對象引用
- 所有的集合類都位於java.util包下,後來為了處理多線程環境下的並發安全問題,java5還在java.util.concurrent包下提供了一些多線程支援的集合類。
- java中集合類都是由兩個介面派生出來的——Collection和Map,其中
Set、Queue和List介面是Collection介面派生出來的子介面,他們分別代表了無序集合和有序集合。
Map介面的所有實作類別用於儲存具有映射關係的資料(關聯陣列)
Collection介面
boolean add(Object o) 失敗返回false。
boolean addAll(Collection o) 用於把集合c中的所有元素添加到指定集合中,失敗分會false。
void clear() 清除集合中所有元素,集合長度變成0。
boolean contains(Object o) 判斷集合裡是否包含指定元素。
boolean containsAll(Collection c) 判斷集合中是否包含了集合c中的所有元素。
boolean isEmpty() 返回集合是否為空白。
Iterator iterator() 返回一個iteratior對象,用於遍曆集合中的所有元素。
boolean remove(Object o) 刪除制定元素o,當集合中存在多個合格元素,僅僅刪除第一個並返回true
boolean removeAll(Collection c) 從集合中刪掉集合c中的所有元素,如果有小於等於一個刪除成功邊返回true。
boolean retainAll(Collection c) 將集合中不是集合c的所有元素刪除,如果有至少一個刪除成功範湖true。
int size() 返回集合中的元素的個數。
Object[] toArray() 將集合轉化為一個數組,所有的集合元素變成對應的數組元素。
1. Set
實際上Set就是Collection,因為本身就是其子介面。唯一的不同就是Set不允許包含重複元素。如果強行加入相同的元素,add()函數會返回false。 無序、不可重複的集合。
在Set介面下有三個實作類別分別是:HashSet、TreeSet和EnumSet,其中HashSet還有一個子類LinkedHashSet。
1.1 HashSet類
HashSet是Set最常見的實作類別。有以下特性:
- 不能保證添加順序,因為原理上HashSet是通過Hash演算法來儲存元素的,所以不能確定順序。
- HashSet不是同步的,假如有多個線程同時修改了HashSet的值必須通過代碼來保證其同步
- 集合元素值可以為NULL。
原理:
- 重點一:添加的元素只能是對象。
- 重點二:對象需要重寫
equals()函數和HashCode()函數。
根據重點有:
- 向HashSet集合中添加一個元素的時候,首先判斷是不是和集合中的某一個元素相等。
- 判斷相等的標準是兩個元素的equals()函數相等。
- 一般來說都需要重寫
equals()和hashCode()函數,尤其注意的是在重寫了hashCode()函數之後,該對象的HashCode值就和該對象的成員變數息息相關了(見後文),在進行任何與該對象的成員變數相關的函數操作的時候一定得注意HashCode的變化。雖然說成員量變數會改變,但是HashCode值卻不會跟著改變!
- 如果都不相等,那就根據對象新計算好的hashCode值選擇儲存位置。這裡也應該注意的是,根據HashCode實際上就是根據成員變數————因為HashCode就是根據成員變數算出來的!
class A {@Overridepublic int hashCode() {return 1;}}class B {@Overridepublic boolean equals(Object obj) {return true;}}class C {@Overridepublic boolean equals(Object obj) {return true;}@Overridepublic int hashCode() {return 2;}}public class MyTest {public static void main(String[] args) {HashSet books = new HashSet();books.add(new A());books.add(new A());books.add(new B());books.add(new B());books.add(new C());books.add(new C());System.out.println(books);}}
運行上面的代碼並根據Set的不重複規則,我們可以想到如果集合即將添加的對象需要重寫equals()函數,但是hashCode()函數並沒有被重寫,會導致兩種結果:
要麼不同hashCode的相同對象被儲存在不同位置上導致集合中出現相同的元素,要麼相同hashCode的不同對象被鏈式儲存到同一個地方。
但是不管哪一種結果都是嚴重不符合Set定義的。
Note about overriding hashCode()a.當兩個對象在比較
equals()函數返回true的時候,
HashCode()函數也應該返回相等的值。
重寫一個對象的equals()函數,通常情況下就是通過比較該對象中的所有執行個體變數。重寫HashCode()也一樣,只不過是使用一定的演算法讓執行個體變數相等變成hashCode相等,我們很容易就想到了線性運算————直接加起來。下面給出基礎資料型別 (Elementary Data Type)的hashCode計算方法:
| 執行個體變數類型 |
計算方式 |
| boolean |
hashCpde = (f?0:1); |
| 整數類型(byte, short, int , char) |
hashCode = (int)f; |
| long |
hashCode = (int)(f^(f>>>32)); |
| float |
hashCode = Float.floatToBits(f); |
| double |
long l = Double.doubleToLongBits(f); hashCode = f.hashCode(); |
| 參考型別 |
hashCode = f.hashCode(); |
將每一個通過第一步計算出來的各個執行個體變數的值乘以一個質數相加(防止直接相加的冗餘)得到該對象的新的HashCode值。
b.
HashSet集合中同一個對象在程式運行過程中必須使用一個
hashCode,一定要避免在過程中對該對象的一些執行個體變數進行修改。
主要是在程式啟動並執行過程中,可能會有修改集合元素中的變數的可能,根據前面重寫演算法和原理部分就可以知道:成員變數、HashCode、equals這三者基本等同起來了。
class A {public int count;public A(int count) {this.count = count;}@Overridepublic boolean equals(Object obj) {if(this == obj) {return true;}if(obj != null && obj.getClass() == A.class) {A a = (A)obj;return this.count == a.count;}return false;}@Overridepublic int hashCode() {return this.count;}@Overridepublic String toString() {return "A[count] "+count + "]";}public class MyTest {public static void main(String[] args) {HashSet books = new HashSet();books.add(new A(5));books.add(new A(-3));books.add(new A(9));books.add(new A(-2));System.out.println(books);Iterator it = books.iterator();A first = (A)it.next();first.count = -3;System.out.println(books);books.remove(new A(-3));System.out.println(books);System.out.println(books.contains(new A(-3)));System.out.println(books.contains(new A(5)));}}
(假設這裡的第一個元素是5)看到上述代碼中,首先將第一個元素的count值改成了-3,這時候便會有兩個count = -3的元素存在,但是!
不要忘了,就算第一個元素的count被改成-3,它的HashCode是5!和最初加入HashSet的時候是一樣的!這一點可以通過輸出整個集合元素的HashCode的時候會發現。
所以當後面即將remove(-3)的時候,實際上集合首先會去找HashCode為-3的儲存位置————這時候只有最初count為-3的那個元素滿足條件,所以刪除的還是正確的,但是之後判斷集合中是否有-3?根據HashCode已經被刪掉了返回false;是否有5?雖然可以根據5找到那個被修改的元素,但是因為它的count值被修改為-3,所以equals也返回false。
LinkedHashSet
- 因為
HashSet是按照Hash演算法來儲存元素的,所以元素的訪問順序往往和儲存順序不一致。有時候弧很不方便。LinkedHashset是HashSet的子類,它唯一的不同就是通過鏈表維護了元素的次序。
- 但是因為使用了鏈表,所以效能稍差一些。
1.2 TreeSet類
Java對象集合