Java學習筆記127——集合—Set集合—HashSet類、LinkedHashSet類、TreeSet類及其自然排序
阿新 • • 發佈:2021-12-23
Set集合
HashSet類
import java.util.HashSet; /* Set集合:元素唯一且元素無序(儲存和取出順序不一致)的集合 HashSet類概述 不保證 set 的迭代順序 特別是它不保證該順序恆久不變。 HashSet如何保證元素唯一性 底層資料結構是雜湊表(元素是連結串列的陣列) 雜湊表依賴於雜湊值儲存 新增功能底層依賴兩個方法: int hashCode() boolean equals(Object obj) Set集合中的元素為什麼不會重複?看新增add()方法的原始碼 */ public class SetDemo1 { public static void main(String[] args) { //用Set的子類去例項化 HashSet<String> arr = new HashSet<>(); //新增元素到集合 arr.add("hello"); arr.add("world"); arr.add("java"); arr.add("bigdata"); arr.add("hadoop"); arr.add("hello"); arr.add("hello"); arr.add("java"); arr.add("spark"); arr.add("flink"); arr.add("world"); arr.add("hadoop"); //增強for迴圈遍歷 for(String s : arr){ System.out.println(s); } } } //----------HashSet<>()中add()的原始碼分析------- public interface Set<E> extends Collection<E>{ } public class HashSet<E> extends AbstractSet<E> implements Set<E>{ private static final Object PRESENT = new Object(); private transient HashMap<E,Object> map; public boolean add(E e) { //E -- String //e -- "hello" return map.put(e, PRESENT)==null; } } public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{ public V put(K key, V value) { //key -- "hello" //value -- new Object() return putVal(hash(key), key, value, false, true); } //簡單理解為呼叫元素類的hashCode()方法計算雜湊值 static final int hash(Object key) { //"hello" int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { //理解為雜湊表儲存的是一個一個的結點陣列 Node<K,V>[] tab; Node<K,V> p; int n, i; //判斷雜湊表是否已經初始化完畢,如果沒有初始化,就在這裡初始化 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //根據元素物件計算好的雜湊值再進行一次與運算,計算出的是該元素儲存在雜湊表中的位置 //如果該元素的位置是null,說明該位置沒有元素,可以進行儲存 //就建立新的結點,儲存元素 //分析到這一步我們得出第一個結論,儲存的位置與元素類中的hashCode()有關。 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { //如果該元素的位置不是null,說明這個位置上已經有元素了,可以確定是雜湊值是一樣的 //但是呢,我們並不能確定兩個值是不是一樣的 Node<K,V> e; K k; //先將存入元素的雜湊值與該位置中元素的雜湊值做比較 //如果雜湊值都不一樣,繼續走判斷instanceof() //如果雜湊值都一樣,會呼叫元素的equals(k)方法進行比較 //如果equals(k)方法進行比較結果是false,繼續向下執行,最終會將元素插入到集合中或者不插入 //如果equals(k)方法進行比較結果是true,表示元素的雜湊值和內容都一樣,表示元素重複了 //就覆蓋,從現象上來看,其實就是不賦值 //說到這裡我們已經知道了add()方法和hashCode()以及equals()方法有關 //會不會去重取決於元素型別有沒有重寫hashCode()以及equals()方法 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } }
import java.util.Objects; public class Student2 { private String name; private int age; public Student2() { } public Student2(String name, int age) { 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; } //重寫equals()和hashCode()方法 @Override public String toString() { return "Student2{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student2 student2 = (Student2) o; return age == student2.age && Objects.equals(name, student2.name); } @Override public int hashCode() { return Objects.hash(name, age); } } import java.util.HashSet; /* 儲存自定義物件並遍歷 */ public class HashSetDemo2 { public static void main(String[] args) { //建立集合物件 HashSet<Student2> hashSet = new HashSet<>(); //建立學生物件 Student2 s1 = new Student2("xiaowang", 18); Student2 s2 = new Student2("xiaowang", 18); Student2 s3 = new Student2("xiaoli", 19); Student2 s4 = new Student2("xiaoliu", 20); hashSet.add(s1); hashSet.add(s2); hashSet.add(s3); hashSet.add(s4); for(Student2 s:hashSet){ System.out.println(s); } //輸出結果發現沒有去重,這是為什麼呢? //根據剛才add()的原始碼分析,如果沒有在Student2類中重寫equals()和hashCode() //比較的就是物件的地址值,而s1到s4的地址值都不一樣,所以都新增。沒有達到去重的目的。 //想要去重,就需要在Student2類中重寫equals()和hashCode()方法 } }
LinkedHashSet類
import java.util.HashMap; import java.util.LinkedHashSet; import java.util.TreeSet; /* public class HashSet<E> implements Set<E>{} public class LinkedHashSet<E> extends HashSet<E>{} LinkedHashSet:底層資料結構是雜湊表和雙向連結串列 雜湊表保證了元素唯一 連結串列保證了元素的有序(儲存和取出順序一致) */ public class LinkedHashSetDemo { public static void main(String[] args) { //建立LinkedHashSet集合物件 LinkedHashSet<String> arr = new LinkedHashSet<>(); //新增元素到集合 arr.add("hello"); arr.add("world"); arr.add("java"); arr.add("bigdata"); arr.add("hadoop"); arr.add("hello"); arr.add("hello"); arr.add("java"); arr.add("spark"); arr.add("flink"); arr.add("world"); arr.add("hadoop"); for (String s : arr){ System.out.println(s); } } }
TreeSet類及其自然排序
import java.util.TreeSet;
/*
TreeSet:元素唯一,元素的順序可以按照某種規則進行排序
兩種排序方式:
自然排序 :無參構造--實現comparable介面
比較器排序 :有參構造--實現Comparator介面
A NavigableSet實現基於TreeMap 。
的元件使用其有序natural ordering ,或由Comparator集合建立時提供,這取決於所使用的構造方法。
要想知道如何去重以及排序,就去看原始碼。
因為Integer類實現了comparable介面,所以它可以做自然排序
*/
public class TreeSetDemo1 {
public static void main(String[] args) {
//建立一個集合物件
TreeSet<Integer> ts = new TreeSet<>();
//新增元素到集合中
ts.add(20);
ts.add(18);
ts.add(23);
ts.add(24);
ts.add(66);
ts.add(12);
ts.add(18);
ts.add(20);
ts.add(23);
ts.add(2);
//遍歷
for(Integer i : ts){
System.out.println(i);
}
}
}
//---------------TreeSet集合的add()方法的原始碼--------------
---------------------------------------
interface Collection {
...
}
interface Set extends Collection {
...
}
---------------------------------------
class TreeSet implements Set {
...
private static final Object PRESENT = new Object();
private transient NavigableMap<E,Object> m;
public TreeSet() {
this(new TreeMap<E,Object>());
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
...
}
---------------------------------------
class TreeMap implements NavigableMap {
...
public V put(K key, V value) {
Entry<K,V> t = root; // 先造根,TreeSet集合底層資料結構是紅黑樹(是一個自平衡的二叉樹)
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator; // 因為用的是TreeSet的無參構造方法,是自然排序,沒有用到comparator比較器
if (cpr != null) { // 所以此時的comparator = null,則程式執行else裡面的程式碼
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
} else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key; // 此介面Comparable強行對實現它的每個類的物件進行整體排序。這種排序被稱為類的自然排序,類的 compareTo 方法被稱為它的自然比較方法。。
do { // 舉例中我們使用的是包裝類Intrger,而Integer類實現了Comparable介面。此例子是向上轉型。
parent = t;
cmp = k.compareTo(t.key); // 類的 compareTo 方法被稱為它的自然比較方法。
if (cmp < 0) // int compareTo(T o) 比較此物件與指定物件的順序。如果該物件小於、等於或大於指定物件,則分別返回負整數、零或正整數。
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
...
}
---------------------------------------
由上可知:真正的比較是依賴於元素的compareTo()方法,而這個方法compareTo()是定義在 Comparable接口裡面的(抽象方法)。
所以,你要想重寫該方法,就必須是先實現 Comparable介面。這個介面表示的就是自然排序。
public class Student3 implements Comparable<Student3> {//為了解決這個問題,讓Student3實現Comparable介面
private String name; //尖括號中放當前元素型別--Student3,並且重寫Comparable介面中的抽象方法
private int age;
public Student3() {
}
public Student3(String name, int age) {
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 "Student2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student3 o) {
// return 0;--只插入第一個元素
// return 1;--永遠都可以插入,沒有去重
// return -1;--也不行,也沒去重
//這裡返回什麼,其實應該根據我們的規則來排序
//比如我想在去重的前提下,按照年齡進行排序
// return this.age - o.age;
//年齡一樣,姓名不一定一樣
//主要條件(題目要求的條件)
int i = this.age - o.age;
//隱含條件(需要自己挖掘)
int i2 = i == 0 ? this.name.compareTo(o.name) : i;
return i2;
}
}
import java.util.TreeSet;
/*
TreeSet儲存學生物件並遍歷
按照正常的寫法,我們一執行就報錯了
java.lang.ClassCastException:型別轉換異常
由於我這裡建立TreeSet物件呼叫的是無參構造方法,所以走的是自然排序
而底層原始碼有一步向下轉型
Comparable<? super K> k = (Comparable<? super K>) key;
報錯了
原因是我們Student3類沒有實現Comparable介面,無法向下轉型,所以報錯了。
*/
public class TreeSetDemo2 {
public static void main(String[] args) {
//建立集合物件
TreeSet<Student3> ts = new TreeSet<>();
//建立學生物件
Student3 s1 = new Student3("周姐",24);
Student3 s2 = new Student3("李元浩",25);
Student3 s3 = new Student3("李湘赫",22);
Student3 s4 = new Student3("漢子哥",26);
Student3 s5 = new Student3("硬幣哥",21);
Student3 s6 = new Student3("烏茲",20);
Student3 s7 = new Student3("李元浩",25);
Student3 s8 = new Student3("廠長",25);
//將學生物件插入到集合中
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
ts.add(s8);
for (Student3 s:ts){
System.out.println(s);
}
}
}