Java Set接口

      一、Set接口的層級結構
       1、特點
       2、層級關係
       二、HashSet
      1、數據結構:哈希表
      2、保證元素唯一性
      3、HashSet添加、刪除、包含判斷依據
      三、TreeSet
      1、TreeSet的介紹
      2、TreeSet存儲自定義對象
      3、數據結構:二叉樹(紅黑樹)
      4、比較器
      5、保證元素唯一性
      

一、Lis接口的層級結構

1、特點
Set是無序不可重複的集合。Set集合與Collection基本相似,它沒有提供額外的方法,可以說Set就是一個Collection 。

注意: 無序指的是:元素的存入和取出的順序不一定一致

2、層級結構

Collection
    |-- Set : 元素時無序的,元素不可以重複。 
       |-- HashSet: 底層數據結構是哈希表,線程是非同步的。
       | --TreeSet:  底層數據結構是二叉樹,可以對Set集合中的元素進行排序

二、HashSet
1、數據結構: 哈希表
Hash是一種數據結構,用戶查找對象。Hash爲每一個對象計算出一個整數,稱爲Hash Code(哈希值)。哈希表是按照哈希值來存的。當我們添加元素時,哈希值是一樣的,這時候,會進行是否是同一對象判斷equals,如果不是同一對象,那麼會在當前對象下順延(串下來的)。
哈希表是一個鏈接式列表的陣列。每一個列表稱爲一個buckets(哈希表元)。對象位置的計算index=HashCode%buckets。

如何查看哈希值?

Demo d1 = new Demo();
System.out.println(obj); 
輸出: Demo@15db9742 

爲什麼是輸出Demo@15db9742這樣的格式?
我們看下Object中的public String toString()方法的實現
Object 類的 toString 方法返回一個字符串,該字符串由類名(對象是該類的一個實例)、at 標記符“@”和此對象哈希碼的無符號十六進制表示組成。換句話說,該方法返回一個字符串,它的值等於:

getClass().getName() + '@' + Integer.toHexString(hashCode())

所以,15db9742 就是哈希值。

1、無序不重複

	public static void main(String[] args) {
		
		HashSet<String> hs = new HashSet<String>();
		sop(hs.add("java1"));  // true
		sop(hs.add("java1")); //false
		hs.add("java2");
		
		Iterator<String> it = hs.iterator();
		while(it.hasNext()){
			sop(it.next());
		}
	}
	
	public static void sop(Object obj){
		System.out.println(obj);
	}

運行結果

true
false
java2
java1     
哈希值一樣,同一對象。添加失敗。存入和取出順序不一致。

2.保證元素唯一性
HashSet是如何保證元素的唯一性呢?
是通過元素的兩個方法,hashCode和equals方法來完成 的。
先計算哈希值。
( 1 ) 如果元素的hashCode值不同,不會調用equals,添加成功
( 2 ) 如果元素的hashCode值相同,纔會判斷equals是否爲true,爲true添加失敗
所以,當我們自定義對象的時候,一般要複寫hashcode和equals方法,因爲自定義對象可能要存放到hashSet集合中。還有,複寫hashCode要儘量保證哈希值的唯一性,一般根據判斷條件生成
注意: 複寫的原則。
下面看一例子: hashSet存自定義對象Person,當姓名和年齡一致,元素重複。

	public static void main(String[] args) {
	    HashSet<Person> hs = new HashSet<Person>();
		hs.add(new Person("a1", 11));
		hs.add(new Person("a2", 22));
		hs.add(new Person("a3", 33));
		hs.add(new Person("a3", 33));
		Iterator<Person> it = hs.iterator();
		while(it.hasNext()){
			Personperson = (Person)it.next();
			sop(person.getName()+"::"+person.getAge());
		}
		
		//重寫了equals,沒重寫hashcode方法。發現equals發現沒被調用,而且都存入成功。說明計算得到是不同的哈希值。
		// 鑑於需求,我們需要去重。說明,我們需要覆蓋hashcode方法,建立自己的哈希值。
		// 哈希值的生成根據判斷條件
		// 重寫hashcode方法,發現,去重成功了。而且equals方法運行了。
}
class Person{
	private String name;
	private int age;
	public Person(String n,int a){
		this.name = n;
		this.age = a;
	}
	
	public String getName(){
		return this.name;
	}
	
	public int getAge(){
		return this.age;
	}
	
	public boolean equals(Object obj){
		if(!(obj instanceof Person))
			return false;
		Personp = (Person)obj;
		System.out.println(this.name+"...."+this.age);
		return this.name.equals(p.getName()) && this.age == p.getAge();
	}
	
	public int hashCode()
	{
		System.out.println(this.name+"...hashcode");
		//return 60; 會有很多重複比較
		return this.name.hashCode()+this.age*13;
	}

3、HashSet添加、刪除、包含判斷依據
添加前面說了。

hs.remove(new Person("a1", 11));
hs.contains(new Person("a2", 12));

刪除:先計算對象new Person("a1", 11)的哈希值,如果哈希值不存在,刪除失敗。如果哈希值存在,調用equals方法,找到對象並刪除對象。
包含 :先計算對象的哈希值。如果哈希值存在,調用equals方法,返回結果。如果不存在,直接返回false。

總結 :HashSet對於判斷元素是否存在以及刪除等操作依賴的方法是元素的hashcode和equals方法。同理,ArrayList判斷元素是否存在以及刪除等操作,只依賴元素的equals方法。原因其實也很簡單,跟數據結構有關。數據結構不同,依賴的方法不一樣。

三、TreeSet

1、TreeSet的介紹
TreeSet是SortedSet接口的唯一實現,可以確保集合元素處於排序狀態。TreeSet支持兩種排序方式:自然排序和定製排序,默認情況下采用自然排序。
下面看一例子:

   public static void main(String[] args) {
   	TreeSet<String> ts = new TreeSet<String>();
   	ts.add("dsa");
   	ts.add("abc");
   	ts.add("a");
   	Iterator<String> it = ts.iterator();
   	while(it.hasNext()){
   		System.out.println(it.next());
   	}
   }

輸出結果:

a
abc
dsa

變換上面add方法的添加順序,你會發現,輸出結果始終不變。

分析:Java提供了一個Compare接口,該接口定義了一個compareTo(Object obj) 方法,該方法返回一個整數值。實現該接口的類就必須實現該方法,實現了該接口的對象就可以比較大小。換句話說,無論你是自定義對象還是系統定義對象,你想放進TreeSet集合,你就必須實現Compareable接口中的compareTo(Object obj) 方法。上述集合添加的元素爲String對象。String就實現了Compareable接口。總歸來說: TreeSet要排序,就要讓元素自身具備比較性。元素需要實現Comparable接口,覆蓋CompareTo方法。
在這裏插入圖片描述
在這裏插入圖片描述
大部分類在實現compareTo(Object obj) 方法,都需要將比較對象obj強制轉化成相同類型,因爲只有相同類的兩個實例才能比較大小。當試圖把一個對象添加到TreeSet中,TreeSet會調用該對象的compareTo(Object obj) 方法與集合中其他元素進行比較。如果不是同一類元素,會發生ClassCastException(類型轉換)異常。當一個對象調用另一個對象進行比較時,例如obj1.compareTo(obj2),如果該方法放回0,則表明兩個對象相等,如果該方法返回一個正整數,表明obj1大於obj2,如果返回一個負整數,表明obj1小於obj2.

2、TreeSet存儲自定義對象
需求: 往TreeSet集合中存自定義對象學生,想按照學生的年齡進行排序。
分析: 年齡一樣,姓名不同的情況要考慮!!!

	public static void main(String[] args) {
		
		TreeSet<Student> ts = new TreeSet<Student>();
		ts.add(new Student("a1",11));
		ts.add(new Student("a2",12));
		ts.add(new Student("a3",13));
		ts.add(new Student("a2",13));
		Iterator<Student> it = ts.iterator();
		while(it.hasNext()){
			System.out.println(it.next());
		}
}

// TrteeSet中的元素要具備比較性,需要實現Comparable接口,實現compareTo方法
class Student implements  Comparable<Student>
{
	private String name;
	private int age;
	public Student(String n,int a){
		this.name = n;
		this.age = a;
	}
	public int compareTo(Student obj){
	/*
		if(this.age > obj.age){
			return 1;
		//如果年齡一樣,不繼續判斷姓名。同年齡不同姓名的元素就存不進去了。
		}else(this.age == obj.age){
			return this.name.compareTo(obj.name);
		}
		return -1;
	*/
	// 下面是優化後的代碼
	int num = new Integer(this.age).compareTo(new Integer(obj.age));
		if(num == 0){
		   return this.name.compareTo(obj.name);
		}
		return num;
	}
	public String toString(){
		return this.name+"::"+this.age;
	}
}

注意: 排序時,當主要條件相同時,一定要判斷次要條件

3、數據結構:二叉樹

既然TreeSet是有序的,那麼如何確定元素的位置呢?

		public static void main(String[] args) {
		TreeSet<Student> ts = new TreeSet<Student>();
		ts.add(new Student("lisi02",22));
		ts.add(new Student("lisi007",20));
		ts.add(new Student("lisi09",19));
		ts.add(new Student("lisi08",19));
		ts.add(new Student("lisi11",40));
		ts.add(new Student("lisi16",30));
		ts.add(new Student("lisi10",29));
		ts.add(new Student("lisi22",90));
		ts.add(new Student("lisi007",20));

      	Iterator<Student> it = ts.iterator();
		while(it.hasNext()){
			System.out.println(it.next());
		}
		}

輸出結果如下:

li09::19
lisi08::19
li007::20
lisi007::20
lisi02::22
lisi10::29
lisi16::30
lisi11::40
lisi22::90

轉化成圖標結構如下(原則:小的放右邊,大的放左邊):
在這裏插入圖片描述

分析:從中我們可以看出,當對象ts.add(new Student("lisi11",40));被添加時候,現與對象new Student("lisi12",20)對比,發現年齡比22大。左邊的都不用對比了。節省了比較次數,提高了性能。二叉樹到元素一多以後,會自動去折中值先進行比較,從中間向兩邊散發

注意 :在TreeSet集合比較對象,它只看Compare結果。 如果,你始終將CompareTo返回值爲1,意思就是怎麼存進去,怎麼取出來;返回-1,就是逆序;返回0,只存進一個元素。

4、比較器 compator
如果一個類不能用於實現 java.lang.Comparable或者不喜歡默認的comparable行爲(改寫系統對象),提供自己的排序順序,我們可以實現Comparator接口來定義一個比較器。
TreeSet的構造方法有如下:在這裏插入圖片描述

我舉一個例子: 字符串根據長度排序。字符串自己實現了Comparable接口,但是並不滿足我們的需求。因此,我們需要自定義比較器。

//自定義比較器
class StrLengthCompare implements Comparator<String>
{	
	public int compare(String obj1,String obj2){
		int num = new Integer(obj1.length()).compareTo(new Integer(obj2.length()));
		if(num == 0){
			return obj1.compareTo(obj2);
		}
		return num;
	}
}
//main方法如下
	public static void main(String[] args) {
		
		TreeSet<String> ts = new TreeSet<String>(new StrLengthCompare());
        ts.add("abcd");
        ts.add("cc");
        ts.add("cba");
        ts.add("z");
        ts.add("helloofa");
        
        Iterator<String> it = ts.iterator();
        while(it.hasNext()){
        	System.out.println(it.next());
        }
       }

5、保證元素唯一性
保證元素唯一性的一句:conpareTo方法return0。

注意:
無論是comparable接口的compareTo方法,還是自定義類去實現compator接口的compare方法,當返回值爲0的時候,僅僅是表示兩個對象在同一位置。對於TreeSet而言,判斷兩個對象不相等的標準是兩個對象通過equals方法比較返回false或者通過compareTo(object obj)比較沒有返回爲0.即使兩個對象爲同一對象,TreeSet也會把他當成兩個對象處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章