一:java.util.Set(interface)java
Set是一種不包含重複的元素的Collection,即任意的兩個元素e1和e2都有e1.equals(e2)=false,Set最多有一個null元素。算法
public interface Set extends Collection { // Query Operations }
接下來將簡單介紹Set下的幾個實現類,如:HashSet,TreeSet,LinkedHashSet。數組
二:java.util.HashSet(class) 安全
對於 HashSet 而言,它是基於 HashMap 實現的,HashSet 底層維護了一個HashMap,其採用 HashMap 來保存全部元素,所以 HashSet 的實現比較簡單,查看 HashSet 的源代碼,能夠看到以下代碼: 多線程
public class HashSet extends AbstractSet implements Set, Cloneable, java.io.Serializable { // 使用 HashMap 的 key 保存 HashSet 中全部元素 private transient HashMap map; // 定義一個虛擬的 Object 對象做爲 HashMap 的 value private static final Object PRESENT = new Object(); ... // 初始化 HashSet,底層會初始化一個 HashMap public HashSet(){ map = new HashMap(); } // 以指定的 initialCapacity、loadFactor 建立 HashSet // 其實就是以相應的參數建立 HashMap public HashSet(int initialCapacity, float loadFactor) { map = new HashMap(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy){ map = new LinkedHashMap(initialCapacity , loadFactor); } // 調用 map 的 keySet 來返回全部的 key public Iterator iterator(){ return map.keySet().iterator(); } // 調用 HashMap 的 size() 方法返回 Entry 的數量,就獲得該 Set 裏元素的個數 public int size(){ return map.size(); } // 調用 HashMap 的 isEmpty() 判斷該 HashSet 是否爲空, // 當 HashMap 爲空時,對應的 HashSet 也爲空 public boolean isEmpty(){ return map.isEmpty(); } // 調用 HashMap 的 containsKey 判斷是否包含指定 key //HashSet 的全部元素就是經過 HashMap 的 key 來保存的 public boolean contains(Object o){ return map.containsKey(o); } // 將指定元素放入 HashSet 中,也就是將該元素做爲 key 放入 HashMap public boolean add(E e){ return map.put(e, PRESENT) == null; } // 調用 HashMap 的 remove 方法刪除指定 Entry,也就刪除了 HashSet 中對應的元素 public boolean remove(Object o){ return map.remove(o)==PRESENT; } // 調用 Map 的 clear 方法清空全部 Entry,也就清空了 HashSet 中全部元素 public void clear(){ map.clear(); } ... }
1. 此類實現 Set 接口,由哈希表(其實是一個 HashMap 實例)支持。ide
2. HashSet按Hash算法來存儲集合中的元素。所以具備很好的存取和查找性能。性能
3. HashSet具備如下特色:測試
- 不能保證元素的排列(迭代)順序 即HashSet的元素存放順序和添加進去時候的順序沒有任何關係;特別是它不保證該順序恆久不變。this
- HashSet不是線程安全的。spa
- 此類容許使用 null 元素,可是不容許出現重複元素。
4. 當想HashSet集合中存入一個元素時,HashSet會調用該對象的hashCode() 方法來獲得該對象的hashCode值,而後根據hashCode值決定該對象在HashSet中的存儲位置。
5. 若是兩個元素的equals()方法值返回ture,但它們的hashCode值返回值不相等,hashSet將會把他們存儲在不一樣的位置。
對HashSet類中一些基本方法的使用,參照API:
首先,先定義一個Person類,完成基本的封裝操做,並提供hashCode()和equals()方法。
public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } /** * 爲何在hashCode()方法中有質數的存在。 * * eg: * Person p1: name: 100, age: 22 * Person p2: name: 22, age: 100 * 若是隻是普通的相加,那麼上述兩個對象的 hashCode值相等, * 可是經過質數prime * 其中某個數,那麼在相加,二者的hashCode值就不相等了。 * * */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } /** * 提供 構造方法,get set方法, equals方法。 * */ }
在定義一個測試類,完成基本方法的使用:
public class Test1HashSet { public static void main(String[] args) { Set<Person> set = new HashSet<Person>(); Person p1 = new Person("Berg",22); Person p2 = new Person("Xujun",22); Person p3 = new Person("Berg",22); Person p4 = new Person("HashSet",23); Person p5 = new Person("SetHash",24); System.out.println( p1.equals(p3)); System.out.println( p1.hashCode() + " \t " + p3.hashCode() ); set.add( p1 ); set.add( p2 ); set.add( p3 ); set.add( p4 ); set.add( p5 ); // 1. 怎麼確保不可重複, 重寫 HashCode() 和 equals(); //2.存的順序與取得順序不一致。 //3.迭代輸出 循環 的方法 //4. 注意輸出結果是無序的。 //5. 若是有序,只須要將 HashSet 改成LinkedHashSet System.out.println( " 輸出第1種方案 : " + set.size() ); for( Person s : set){ System.out.println( s + " " +s.hashCode()); } System.out.println( " 輸出第二種方案 : "); Object [] objs = set.toArray(); // toArray() 返回一個包含 set 中全部元素的數組。 for( Object o : objs){ System.out.println( o ); // o.hashCode() } // 迭代 : System.out.println( " 輸出第3種方案 : "); // 接口不能實例化 , //返回對此 set 中元素進行迭代的迭代器。 Iterator<Person> its = set.iterator(); while( its.hasNext() == true){ //若是仍有元素能夠迭代,則返回 true。 Person s = its.next(); //返回迭代的下一個元素。 System.out.println( s ); } } }
三:java.util.LinkedHashSet(class)
具備可預知迭代順序的 Set 接口的哈希表和連接列表實現。此實現與 HashSet 的不一樣以外在於,後者維護着一個運行於全部條目的雙重連接列表。此連接列表定義了迭代順序,即按照將元素插入到 set 中的順序(插入順序)進行迭代。注意,插入順序不 受在 set 中從新插入的 元素的影響。(若是在 s.contains(e) 返回 true 後當即調用 s.add(e),則元素 e 會被從新插入到 set s 中。)
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable {}
1. LinkedHashSet是HashSet的子類。
2. LinkedHashSet集合根據元素的hashCode值來決定元素的存儲位置,但他同時使用鏈表維護元素的次序,這使得元素看起來是以插入順序保存的。
3. LinkedHashSet 插入性能略低於HashSet,但在迭代訪問Set裏的所有元素時有很好的性能。
4.LinkedHashSet不容許集合元素重複。
5.此實現也不一樣步,即線程不安全的。
eg: 將上述HashSet代碼實現中 註釋部分的第五點 要求知足便可,即:
將HashSet 更改成 LinkedHashSet便可看到迭代輸出結果是有序的,且元素不重複。
四:java.util.TreeSet<E>
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable { //... TreeSet(NavigableMap<E,Object> m) { this.m = m; } public TreeSet() { this(new TreeMap<E,Object>()); } //... }
基於TreeMap的NavigableSet實現。使用元素的天然順序對元素進行排序。固然也能夠本身定製排序方法。
4.1 天然排序:
在原有的Person類中,讓其實現 Comparable 接口,具體實現 compareTo()方法。
package com.berg.se.bean; public class Person implements Comparable<Person> { private String name; private int age; //.....省略 @Override public int compareTo(Person p) { if(p instanceof Person ){ // return this.name.compareTo( p.name); 按升序排序 return p.name.compareTo( this.name ); // 按降序排序 根據名字 }else{ throw new ClassCastException("非Person類型。"); } } }
注意:
public int compareTo( T o):
若返回0,表明兩個元素相等,若返回正數,表明當前元素大,若返回負數,表明當前元素小
TreeSet會調用每一個元素的compareTo()方法去和集合中的每一個已經存在的元素去比較,進而決定當前元素在集合中的位置。
而後在看看具體的TreeSet操做:
public class Test3TreeSet { public static void main(String[] args) { TreeSet<Person> ts = new TreeSet<Person>(); Person p1 = new Person("AA", 19); Person p2 = new Person("BB", 20); Person p3 = new Person("CC", 21); Person p4 = new Person("EE", 23); Person p5 = new Person("DD", 19); Person p6 = new Person("AA", 19); ts.add(p1); ts.add(p5); ts.add(p4); ts.add(p3); ts.add(p2); ts.add(p6); Iterator<Person> iterator = ts.iterator(); while (iterator.hasNext()) { Person p = iterator.next(); System.out.println(p); } //返回此 set 的部分視圖,其元素從 fromElement(包括)到 toElement(不包括)。 System.out.println( " \n*************************部分視圖: \n"); SortedSet<Person> ss = ts.subSet(p4,p2); Iterator<Person> iterator1 = ss.iterator(); while (iterator1.hasNext()) { Person p = iterator1.next(); System.out.println(p); } } }
4.2 定製排序:
不讓其Person類實現Comparable接口。而是將Comparator這個對象當作參數傳遞給TreeSet,讓其元素實現排序效果。能夠下降耦合。
Person2 並無實現 Comparable接口。
public class Test3TreeSet2 { public static void main(String[] args) { Comparator<Object> comparator = new Comparator<Object>() { @Override public int compare(Object o1, Object o2) { if( o1 instanceof Person2 && o2 instanceof Person2){ Person2 p1 = (Person2) o1; Person2 p2 = (Person2) o2; return p2.getAge() - p1.getAge(); }else{ throw new ClassCastException("非Person2類型。"); } } }; TreeSet<Person2> ts = new TreeSet<Person2>(comparator); Person2 p1 = new Person2("AA", 19); Person2 p2 = new Person2("BB", 20); Person2 p3 = new Person2("CC", 21); Person2 p4 = new Person2("EE", 23); Person2 p5 = new Person2("DD", 19); Person2 p6 = new Person2("AA", 19); ts.add(p1); ts.add(p5); ts.add(p4); ts.add(p3); ts.add(p2); ts.add(p6); Iterator<Person2> iterator = ts.iterator(); while (iterator.hasNext()) { Person2 p = iterator.next(); System.out.println(p); } } }
再來了解下TreeSet與TreeMap的區別於聯繫:
TreeMap 和 TreeSet 是 Java Collection Framework 的兩個重要成員,其中 TreeMap 是 Map 接口的經常使用實現類,而 TreeSet 是 Set 接口的經常使用實現類。雖然 TreeMap 和TreeSet 實現的接口規範不一樣,但 TreeSet 底層是經過 TreeMap 來實現的(如同HashSet底層是是經過HashMap來實現的同樣),所以兩者的實現方式徹底同樣。而 TreeMap 的實現就是紅黑樹算法。
1. TreeSet和TreeMap的關係
與HashSet徹底相似,TreeSet裏面絕大部分方法都市直接調用TreeMap方法來實現的。
相同點:
TreeMap和TreeSet都是有序的集合,也就是說他們存儲的值都是排好序的。
TreeMap和TreeSet都是非同步集合,所以他們不能在多線程之間共享,不過可使用方法Collections.synchroinzedMap()來實現同步
運行速度都要比Hash集合慢,他們內部對元素的操做時間複雜度爲O(logN),而HashMap/HashSet則爲O(1)。
不一樣點:
最主要的區別就是TreeSet和TreeMap分別實現Set和Map接口
TreeSet只存儲一個對象,而TreeMap存儲兩個對象Key和Value(僅僅key對象有序)
TreeSet中不能有重複對象,而TreeMap中能夠存在
TreeMap的底層採用紅黑樹的實現,完成數據有序的插入,排序。
********************************************************************************************************
注意hashCode方法:
1. HashSet集合判斷兩個元素相等的標準:兩個對象經過equals()方法比較相等,而且兩個對象的hashCode()方法返回值也相等,
2. 若是兩個對象經過equals()方法返回true,這兩個對象的hashCode()值也應該相等。
3. 重寫hashCode()方法時基本原則:
- 同一個對象屢次調用hashCode()方法應該返回相同的值。
- 當兩個對象的equals()方法比較返回true時,這兩個對象的hashCode()方法的返回值也應該相等。
- 對象中用做equals()方法比較的字段,都應該用來計算hashCode值。