上一篇,咱們介紹Java中的List集合。本篇,讓咱們繼續學習,來了解下Set集合;java
Set繼承於Collection接口,是一個不容許出現重複元素,而且無序的集合,主要有HashSet和TreeSet兩大實現類。數組
在判斷重複元素的時候,Set集合會調用hashCode()和equal()方法來實現。安全
HashSet是哈希表結構,主要利用HashMap的key來存儲元素,計算插入元素的hashCode來獲取元素在集合中的位置;app
TreeSet是紅黑樹結構,每個元素都是樹中的一個節點,插入的元素都會進行排序;框架
Set集合框架結構:ide
與List接口同樣,Set接口也提供了集合操做的基本方法。學習
但與List不一樣的是,Set還提供了equals(Object o)和hashCode(),供其子類重寫,以實現對集合中插入重複元素的處理;測試
public interface Set<E> extends Collection<E> { A:添加功能 boolean add(E e); boolean addAll(Collection<? extends E> c); B:刪除功能 boolean remove(Object o); boolean removeAll(Collection<?> c); void clear(); C:長度功能 int size(); D:判斷功能 boolean isEmpty(); boolean contains(Object o); boolean containsAll(Collection<?> c); boolean retainAll(Collection<?> c); E:獲取Set集合的迭代器: Iterator<E> iterator(); F:把集合轉換成數組 Object[] toArray(); <T> T[] toArray(T[] a); //判斷元素是否重複,爲子類提升重寫方法 boolean equals(Object o); int hashCode(); }
HashSet實現Set接口,底層由HashMap(後面講解)來實現,爲哈希表結構,新增元素至關於HashMap的key,value默認爲一個固定的Object。在我看來,HashSet至關於一個閹割版的HashMap;this
當有元素插入的時候,會計算元素的hashCode值,將元素插入到哈希表對應的位置中來;spa
它繼承於AbstractSet,實現了Set, Cloneable, Serializable接口。
(1)HashSet繼承AbstractSet類,得到了Set接口大部分的實現,減小了實現此接口所需的工做,其實是又繼承了AbstractCollection類;
(2)HashSet實現了Set接口,獲取Set接口的方法,能夠自定義具體實現,也能夠繼承AbstractSet類中的實現;
(3)HashSet實現Cloneable,獲得了clone()方法,能夠實現克隆功能;
(4)HashSet實現Serializable,表示能夠被序列化,經過序列化去傳輸,典型的應用就是hessian協議。
具備以下特色:
不容許出現重複因素;
容許插入Null值;
元素無序(添加順序和遍歷順序不一致);
線程不安全,若2個線程同時操做HashSet,必須經過代碼實現同步;
HashSet底層由HashMap實現,插入的元素被當作是HashMap的key,根據hashCode值來肯定集合中的位置,因爲Set集合中並無角標的概念,因此並無像List同樣提供get()方法。當獲取HashSet中某個元素時,只能經過遍歷集合的方式進行equals()比較來實現;
public class HashSetTest { public static void main(String[] agrs){ //建立HashSet集合: Set<String> hashSet = new HashSet<String>(); System.out.println("HashSet初始容量大小:"+hashSet.size()); //元素添加: hashSet.add("my"); hashSet.add("name"); hashSet.add("is"); hashSet.add("jiaboyan"); hashSet.add(","); hashSet.add("hello"); hashSet.add("world"); hashSet.add("!"); System.out.println("HashSet容量大小:"+hashSet.size()); //迭代器遍歷: Iterator<String> iterator = hashSet.iterator(); while (iterator.hasNext()){ String str = iterator.next(); System.out.println(str); } //加強for循環 for(String str:hashSet){ if("jiaboyan".equals(str)){ System.out.println("你就是我想要的元素:"+str); } System.out.println(str); } //元素刪除: hashSet.remove("jiaboyan"); System.out.println("HashSet元素大小:" + hashSet.size()); hashSet.clear(); System.out.println("HashSet元素大小:" + hashSet.size()); //集合判斷: boolean isEmpty = hashSet.isEmpty(); System.out.println("HashSet是否爲空:" + isEmpty); boolean isContains = hashSet.contains("hello"); System.out.println("HashSet是否爲空:" + isContains); } }
Set集合不容許添加劇復元素,那麼究竟是個怎麼狀況呢?
來看一個簡單的例子:
public class HashSetTest { public static void main(String[] agrs){ //hashCode() 和 equals()測試: hashCodeAndEquals(); } public static void hashCodeAndEquals(){ //第一個 Set集合: Set<String> set1 = new HashSet<String>(); String str1 = new String("jiaboyan"); String str2 = new String("jiaboyan"); set1.add(str1); set1.add(str2); System.out.println("長度:"+set1.size()+",內容爲:"+set1); //第二個 Set集合: Set<App> set2 = new HashSet<App>(); App app1 = new App(); app1.setName("jiaboyan"); App app2 = new App(); app2.setName("jiaboyan"); set2.add(app1); set2.add(app2); System.out.println("長度:"+set2.size()+",內容爲:"+set2); //第三個 Set集合: Set<App> set3 = new HashSet<App>(); App app3 = new App(); app3.setName("jiaboyan"); set3.add(app3); set3.add(app3); System.out.println("長度:"+set3.size()+",內容爲:"+set3); } }
測試結果:
長度:1,內容爲:[jiaboyan] 長度:2,內容爲:[com.jiaboyan.collection.App@efb78af, com.jiaboyan.collection.App@5f3306ad] 長度:1,內容爲:[com.jiaboyan.collection.App@1fb030d8]
能夠看到,第一個Set集合中最終只有一個元素;第二個Set集合保留了2個元素;第三個集合也只有1個元素;
到底是什麼緣由呢?
讓咱們來看看HashSet的add(E e)方法:
public boolean add(E e) { return map.put(e, PRESENT)==null; }
在底層HashSet調用了HashMap的put(K key, V value)方法:
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
經過查看以上的源碼,咱們能夠了解到:實際的邏輯都是在HashMap的put()方法中。
int hash = hash(key) 對傳入的key計算hash值;
int i = indexFor(hash, table.length) 對hash值進行轉換,轉換成數組的index(HashMap中底層存儲使用了Entry<K,V>[]數組);
for (Entry<K,V> e = table[i]; e != null; e = e.next) 判斷對應index下是否存在元素;
若是存在,則if(e.hash == hash && ((k = e.key) == key || key.equals(k)))判斷;
若是不存在,則addEntry(hash, key, value, i)直接添加;
簡單歸納以下:
在向HashMap中添加元素時,先判斷key的hashCode值是否相同,若是相同,則調用equals()、==進行判斷,若相同則覆蓋原有元素;若是不一樣,則直接向Map中添加元素;
反過來,咱們在看下上面的例子:
在第一個Set集合中,咱們new了兩個String對象,賦了相同的值。當傳入到HashMap中時,key均爲「jiaboyan」,因此hash和i的值都相同。進行if (e.hash == hash && ((k = e.key) == key || key.equals(k)))判斷,因爲String對象重寫了equals()方法,因此在((k = e.key) == key || key.equals(k))判斷時,返回了true,因此第二次的插入並不會增長Set集合的長度;
第二個Set集合中,也是new了兩個對象,但沒有重寫equals()方法(底層調用的Object的equals(),也就是==判斷),因此會增長2個元素;
第三個Set集合中,只new了一個對象,調用的兩次add方法都添加的這個新new的對象,因此也只是保留了1個元素;
從名字上能夠看出,此集合的實現和樹結構有關。與HashSet集合相似,TreeSet也是基於Map來實現,具體實現TreeMap(後面講解),其底層結構爲紅黑樹(特殊的二叉查找樹);
與HashSet不一樣的是,TreeSet具備排序功能,分爲天然排序(123456)和自定義排序兩類,默認是天然排序;在程序中,咱們能夠按照任意順序將元素插入到集合中,等到遍歷時TreeSet會按照必定順序輸出--倒序或者升序;
它繼承AbstractSet,實現NavigableSet, Cloneable, Serializable接口。
(1)與HashSet同理,TreeSet繼承AbstractSet類,得到了Set集合基礎實現操做;
(2)TreeSet實現NavigableSet接口,而NavigableSet又擴展了SortedSet接口。這兩個接口主要定義了搜索元素的能力,例如給定某個元素,查找該集合中比給定元素大於、小於、等於的元素集合,或者比給定元素大於、小於、等於的元素個數;簡單地說,實現NavigableSet接口使得TreeSet具有了元素搜索功能;
(3)TreeSet實現Cloneable接口,意味着它也能夠被克隆;
(4)TreeSet實現了Serializable接口,能夠被序列化,可使用hessian協議來傳輸;
具備以下特色:
對插入的元素進行排序,是一個有序的集合(主要與HashSet的區別);
底層使用紅黑樹結構,而不是哈希表結構;
容許插入Null值;
不容許插入重複元素;
線程不安全;
public class TreeSetTest { public static void main(String[] agrs){ TreeSet<String> treeSet = new TreeSet<String>(); System.out.println("TreeSet初始化容量大小:"+treeSet.size()); //元素添加: treeSet.add("my"); treeSet.add("name"); treeSet.add("jiaboyan"); treeSet.add("hello"); treeSet.add("world"); treeSet.add("1"); treeSet.add("2"); treeSet.add("3"); System.out.println("TreeSet容量大小:" + treeSet.size()); System.out.println("TreeSet元素順序爲:" + treeSet.toString()); //增長for循環遍歷: for(String str:treeSet){ System.out.println("遍歷元素:"+str); } //迭代器遍歷:升序 Iterator<String> iteratorAesc = treeSet.iterator(); while(iteratorAesc.hasNext()){ String str = iteratorAesc.next(); System.out.println("遍歷元素升序:"+str); } //迭代器遍歷:降序 Iterator<String> iteratorDesc = treeSet.descendingIterator(); while(iteratorDesc.hasNext()){ String str = iteratorDesc.next(); System.out.println("遍歷元素降序:"+str); } //元素獲取:實現NavigableSet接口 String firstEle = treeSet.first();//獲取TreeSet頭節點: System.out.println("TreeSet頭節點爲:" + firstEle); // 獲取指定元素以前的全部元素集合:(不包含指定元素) SortedSet<String> headSet = treeSet.headSet("jiaboyan"); System.out.println("jiaboyan節點以前的元素爲:"+headSet.toString()); //獲取給定元素之間的集合:(包含頭,不包含尾) SortedSet subSet = treeSet.subSet("1","world"); System.out.println("1--jiaboan之間節點元素爲:"+subSet.toString()); //集合判斷: boolean isEmpty = treeSet.isEmpty(); System.out.println("TreeSet是否爲空:"+isEmpty); boolean isContain = treeSet.contains("who"); System.out.println("TreeSet是否包含who元素:"+isContain); //元素刪除: boolean jiaboyanRemove = treeSet.remove("jiaboyan"); System.out.println("jiaboyan元素是否被刪除"+jiaboyanRemove); //集合中不存在的元素,刪除返回false boolean whoRemove = treeSet.remove("who"); System.out.println("who元素是否被刪除"+whoRemove); //刪除並返回第一個元素:若是set集合不存在元素,則返回null String pollFirst = treeSet.pollFirst(); System.out.println("刪除的第一個元素:"+pollFirst); //刪除並返回最後一個元素:若是set集合不存在元素,則返回null String pollLast = treeSet.pollLast(); System.out.println("刪除的最後一個元素:"+pollLast); treeSet.clear();//清空集合: } }
在前面的章節,咱們講到了TreeSet是一個有序集合,能夠對集合元素排序,其中分爲天然排序和自定義排序,那麼這兩種方式如何實現呢?
首先,咱們經過JDK提供的對象來展現,咱們使用String、Integer:
public class TreeSetTest { public static void main(String[] agrs){ naturalSort(); } //天然排序順序:升序 public static void naturalSort(){ TreeSet<String> treeSetString = new TreeSet<String>(); treeSetString.add("a"); treeSetString.add("z"); treeSetString.add("d"); treeSetString.add("b"); System.out.println("字母順序:" + treeSetString.toString()); TreeSet<Integer> treeSetInteger = new TreeSet<Integer>(); treeSetInteger.add(1); treeSetInteger.add(24); treeSetInteger.add(23); treeSetInteger.add(6); System.out.println(treeSetInteger.toString()); System.out.println("數字順序:" + treeSetString.toString()); } }
測試結果:
字母順序:[a, b, d, z] 數字順序:[1, 6, 23, 24]
接下來,咱們自定義對象,看可否實現:
public class App{ private String name; private Integer age; public App(){} public App(String name,Integer age){ this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public static void main(String[] args ){ System.out.println( "Hello World!" ); } } public class TreeSetTest { public static void main(String[] agrs){ customSort(); } //自定義排序順序:升序 public static void customSort(){ TreeSet<App> treeSet = new TreeSet<App>(); //排序對象: App app1 = new App("hello",10); App app2 = new App("world",20); App app3 = new App("my",15); App app4 = new App("name",25); //添加到集合: treeSet.add(app1); treeSet.add(app2); treeSet.add(app3); treeSet.add(app4); System.out.println("TreeSet集合順序爲:"+treeSet); } }
測試結果:
拋出異常:提示App不能轉換爲Comparable對象: Exception in thread "main" java.lang.ClassCastException: com.jiaboyan.collection.App cannot be cast to java.lang.Comparable
爲何會報錯呢?
compare(key, key); // type (and possibly null) check final int compare(Object k1, Object k2) { return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2); }
經過查看源碼發現,在TreeSet調用add方法時,會調用到底層TreeMap的put方法,在put方法中會調用到compare(key, key)方法,進行key大小的比較;
在比較的時候,會將傳入的key進行類型強轉,因此當咱們自定義的App類進行比較的時候,天然就會拋出異常,由於App類並無實現Comparable接口;
將App實現Comparable接口,在作比較:
public class App implements Comparable<App>{ private String name; private Integer age; public App(){} public App(String name,Integer age){ this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } //自定義比較:先比較name的長度,在比較age的大小; public int compareTo(App app) { //比較name的長度: int num = this.name.length() - app.name.length(); //若是name長度同樣,則比較年齡的大小: return num == 0 ? this.age - app.age : num; } @Override public String toString() { return "App{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
測試結果以下:
TreeSet集合順序爲:[App{name='my', age=15}, App{name='name', age=25}, App{name='hello', age=10}, App{name='world', age=20}]
此外,還有另外一種方式,那就是實現Comparetor<t>接口,並重寫compare方法;
//自定義App類的比較器: public class AppComparator implements Comparator<App> { //比較方法:先比較年齡,年齡若相同在比較名字長度; public int compare(App app1, App app2) { int num = app1.getAge() - app2.getAge(); return num == 0 ? app1.getName().length() - app2.getName().length() : num; } }
此時,App不用在實現Comparerable接口了,單純的定義一個類便可;
public class App{ private String name; private Integer age; public App(){} public App(String name,Integer age){ this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public static void main(String[] args ){ System.out.println( "Hello World!" ); } } public class TreeSetTest { public static void main(String[] agrs){ customSort(); } //自定義比較器:升序 public static void customComparatorSort(){ TreeSet<App> treeSet = new TreeSet<App>(new AppComparator()); //排序對象: App app1 = new App("hello",10); App app2 = new App("world",20); App app3 = new App("my",15); App app4 = new App("name",25); //添加到集合: treeSet.add(app1); treeSet.add(app2); treeSet.add(app3); treeSet.add(app4); System.out.println("TreeSet集合順序爲:"+treeSet); } }
測試結果:
TreeSet集合順序爲:[App{name='hello', age=10}, App{name='my', age=15}, App{name='world', age=20}, App{name='name', age=25}]
最後,在說下關於compareTo()、compare()方法:
結果返回大於0時,方法前面的值大於方法中的值; 結果返回等於0時,方法前面的值等於方法中的值; 結果返回小於0時,方法前面的值小於方法中的值;