一、簡介
Set集合中的元素是無序的且不可重複, 如果試圖把兩個相同元素加入同一個Set集合中,則添加操作失敗,add()方法返回false,且新元素不會被加入。
二、HashSet類
HashSet底層數據結構是哈希表
,因此具有很好的存取和查找性能。
哈希表
:一個元素爲鏈表的數組,綜合了鏈表(存儲速度快)和數組(查詢速度快)的優點。
哈希表的存取原理:
1. 調用對象的hashCode()方法,獲得要存儲元素的哈希值。
2. 將哈希值與表的長度(即數組的長度)進行求餘運算得到一個整數值,該值就是新元素要存放的位置(即是索引值)。
- 如果索引值對應的位置上沒有存儲任何元素,則直接將元素存儲到該位置上。
- 如果索引值對應的位置上已經存儲了元素,則執行第3步。
3.遍歷該位置上的所有舊元素,依次比較每個舊元素的哈希值和新元素的哈希值是否相同。
- 如果有哈希值相同的舊元素,則執行第4步。
- 如果沒有哈希值相同的舊元素,則執行第5步。
4.比較新元素和舊元素的地址是否相同
如果地址值相同則用新的元素替換老的元素。停止比較。
如果地址值不同,則新元素調用equals方法與舊元素比較內容是否相同。
- 如果返回true,用新的元素替換老的元素,停止比較。
- 如果返回false,則回到第3步繼續遍歷下一個舊元素。
5.說明沒有重複,則將新元素存放到該位置上並讓新元素記住之前該位置的元素。
HashSet特點:
- 無序
- 集合中的元素值可以是null
- hashSet不是同步的,如果多個線程同時訪問一個Set,只要有一個線程修改了Set中的值,就必須進行同步處理,通常通過同步封裝這個Set對象來完成同步,如果不存在這樣的對象,可以使用Collections.synchronizedSet()方法完成。
實體類:
public class Person {
String name;
int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
測試代碼:
@Test
public void testHashSet(){
Person p1 = new Person("鍾梅", 25);
Person p2 = new Person("王興", 34);
Person p3 = new Person("張三", 18);
Person p4 = new Person("李四", 21);
Person p5 = new Person("李四", 21);
HashSet<Person> hashSet = new HashSet<>();
hashSet.add(p1);
hashSet.add(p2);
hashSet.add(p3);
hashSet.add(p4);
hashSet.add(p5);
for (Person person : hashSet) {
System.out.println(person.getName()+ "----------" + person.getAge());
}
}
輸出結果:
由上可以看到,結果中出現重複元素。在實體類Person中重寫hashCode和equals方法:
//判斷判斷兩個對象是否相等,對象是否存在,對象的name和age是否相等
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
//返回對象的name和age的hash值
@Override
public int hashCode() {
return Objects.hash(name, age);
}
重寫之後不是判斷兩個對象hashCode是否相等,而是判斷對象的name和age是否同時相等,如果同時相等則判斷爲同一對象,不能重複出現在集合中。
再次遍歷集合,運行結果:
可以看到重複的元素已經被覆蓋,保證了集合中元素的唯一性。
爲什麼不直接使用數組,而用HashSet呢?
因爲數組的索引是連續的而且數組的長度是固定的,無法自由增加數組的長度。而HashSet就不一樣了,HashCode表用每個元素的hashCode值來計算其存儲位置,從而可以自由增加HashCode的長度,並根據元素的hashCode值來訪問元素。而不用一個個遍歷索引去訪問,這就是它比數組快的原因。
LinkedHashSet類
LinkedHashSet集合也是根據元素的hashCode值來決定元素的存儲位置,但它同時使用鏈表維護元素的次序,這樣使得元素看起來是以插入的順序保存的,也就是說當遍歷集合LinkedHashSet集合裏的元素時,集合將會按元素的添加順序來訪問集合裏的元素。
輸出集合裏的元素時,元素順序總是與添加順序一致。但是LinkedHashSet依然是HashSet,因此它不允許集合重複。
三、TreeSet類
TreeSet可以確保集合元素處於排序狀態。
內部存儲機制
TreeSet內部實現的是紅黑樹,默認整形排序爲從小到大。
與HashSet集合相比,TreeSet還提供了幾個額外方法:
//如果TreeSet採用了定製順序,則該方法返回定製排序所使用的Comparator,如果TreeSet採用自然排序,則返回null;
Comparator comparator();
//返回集合中的第一個元素;
Object first();
//返回集合中的最後一個元素;
Object last();
//返回指定元素之前的元素。
Object lower(Object e);
//返回指定元素之後的元素。
Object higher(Object e);
//返回此Set的子集合,含頭不含尾;
SortedSet subSet(Object fromElement,Object toElement);
//返回此Set的子集,由小於toElement的元素組成;
SortedSet headSet(Object toElement);
//返回此Set的子集,由大於fromElement的元素組成;
SortedSet tailSet(Object fromElement);
用法示例:
@Test
public void testTreeSet(){
TreeSet<Integer> nums = new TreeSet<>();
//向集合中添加元素
nums.add(5);
nums.add(2);
nums.add(15);
nums.add(-4);
//輸出集合,可以看到元素已經處於排序狀態
System.out.println(nums);//[-4, 2, 5, 15]
System.out.println("集合中的第一個元素:"+nums.first());//集合中的第一個元素:-4
System.out.println("集合中的最後一個元素:"+nums.last());//集合中的最後一個元素:15
System.out.println("集合小於4的子集,不包含4:"+nums.headSet(4));//集合小於4的子集,不包含4:[-4, 2]
System.out.println("集合大於5的子集:"+nums.tailSet(2));//集合大於5的子集:[2, 5, 15]
System.out.println("集合中大於等於-3,小於4的子集:"+nums.subSet(-3,4));//集合中大於等於-3,小於4的子集:[2]
}
輸出結果:
從上面的運行結果可以看出輸出的集合已經按從小到大排好了,但是問題來了,只能從小到大排序嗎?如果是字符對象應按該怎樣的順序排序?如果是一個對象又按怎樣的順序排序呢?遵循怎樣的排序規則呢?
針對這個問題,TreeSet支持兩種排序方法:自然排序和定製排序,在默認情況下,採用的是自然排序。
自然排序
TreeSet會調用集合元素的compareTo(Objec obj)方法來比較元素之間的大小關係,然後將集合元素按升序排列,這就是自然排序。
拓展:
Java提供了一個Comparable接口,該接口裏定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現該接口的類必須實現該方法,實現了該接口的類必須實現該方法,實現接口的類就可以比較大小了。當調用一個一個對象調用該方法與另一個對象進行比較時, compareTo(Object obj)如果返回0表示兩個對象相等;如果返回正整數則表明obj1大於obj2,如果是負整數則相反。
案例:
實現存儲Person類的集合,排序方式,按年齡大小,如果年齡相等,則按name字符串長度,如果長度相等則比較字符。如果name和age都相等則視爲同一對象。
public class Person implements Comparable<Person>{
String name;
int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Person o) {
//比較age
int num=this.age-o.age;
//如果age相等則比較name長度
int num1=num==0?this.name.length()-o.name.length():num;
//如果前兩者都相等則比較name字符串
int num2=num1==0?this.name.compareTo(o.name):num1;
return num2;
}
}
測試:
@Test
public void testTreeSet(){
TreeSet<Person> tree = new TreeSet<>();
//向集合中添加元素
tree.add(new Person("孫悟空",16));
tree.add(new Person("孫悟空",17));
tree.add(new Person("孫悟空",16));
tree.add(new Person("唐僧",16));
tree.add(new Person("沙悟淨",23));
tree.add(new Person("唐僧",30));
//遍歷
System.out.println(tree);
}
輸出:
從運行結果可以看到滿足定義的排序規則。
當把一個對象添加進集合時,集合調用該對象的CompareTo(Object obj)方法與容器中的其他對象比較大小,然後根據紅黑樹結構中找到它的存儲位置。如果兩個對象相等則新對象無法加入到集合中。
定製排序
TreeSet的自然排序是根據集合元素的大小,TreeSet將它們以升序排列。如果需要實現定製排序,例如降序排序,則可通過Comparator接口的幫助。該接口裏包含一個int compare(T o1,T o2)方法,用於比較o1和o2的大小。由於Comparator是一個函數式接口,因此還可以使用Lambda表達式來代替Comparator子類對象。
@Test
public void testTreeSet2(){
TreeSet<Integer> nums = new TreeSet<>((a,b)->-(a-b));
//向集合中添加元素
nums.add(5);
nums.add(2);
nums.add(15);
nums.add(-4);
//輸出集合,可以看到元素已經處於排序狀態
System.out.println(nums);//[15, 5, 2, -4]
}
以上就是我查看資料後的一些整理,有什麼問題請評論區留言。
轉載自: https://blog.csdn.net/mashaokang1314/article/details/83721792