一、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也會把他當成兩個對象處理。