集合(三):Set

一: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值。

相關文章
相關標籤/搜索