1、Set接口
1)Set接口概述
一個不包含重復元素的 collection,無序(存儲順序和取出順序不一致),唯一。 (List有序,即存儲順序和取出順序一致,可重復)
2)Set案例
存儲字符串並遍歷
存儲自定義對象並遍歷
2、HashSet
1)HashSet類概述
不保證 set 的迭代順序;特別是它不保證該順序恆久不變。
2)HashSet如何保證元素唯一性
底層數據結構是哈希表(元素是鏈表的數組)
哈希表依賴於哈希值存儲
添加功能底層依賴兩個方法:
· int hashCode()
· boolean equals(Object obj)
例子1:存儲字符串
package setdemos; import java.util.HashSet; import java.util.Set; /** * Created by gao on 15-12-17. */ public class HashSetDemo01 { public static void main(String[] args) { //創建集合對象 Set<String> set = new HashSet<String>(); //創建並添加元素 set.add("hello"); set.add("java"); set.add("world"); set.add("java"); set.add("android"); set.add("hello"); //增強for for(String s : set){ System.out.println(s); } } }
輸出結果:(不存儲重復的)
hello
android
java
world
例子2:存儲自定義對象
學生類:重寫hashCode()方法和equals()方法
package setdemos; /** * @author Administrator * */ public class Student { private String name; private int age; public Student() { super(); } public Student(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 boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Student)) return false; Student student = (Student) o; if (age != student.age) return false; if (!name.equals(student.name)) return false; return true; } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + age; return result; } }
測試類:
package setdemos; import java.util.HashSet; /** * Created by gao on 15-12-17. */ /* * 需求:存儲自定義對象,並保證元素的唯一性 * 要求:如果兩個對象的成員變量值都相同,則為同一個元素。 * * 目前是不符合我的要求的:因為我們知道HashSet底層依賴的是hashCode()和equals()方法。 * 而這兩個方法我們在學生類中沒有重寫,所以,默認使用的是Object類。 * 這個時候,他們的哈希值是不會一樣的,根本就不會繼續判斷,執行了添加操作。 */ public class HashSetDemo02 { public static void main(String[] args) { // 創建集合對象 HashSet<Student> hs = new HashSet<Student>(); // 創建學生對象 Student s1 = new Student("林青霞", 27); Student s2 = new Student("柳岩", 22); Student s3 = new Student("王祖賢", 30); Student s4 = new Student("林青霞", 27); Student s5 = new Student("林青霞", 20); Student s6 = new Student("范冰冰", 22); // 添加元素 hs.add(s1); hs.add(s2); hs.add(s3); hs.add(s4); hs.add(s5); hs.add(s6); // 遍歷集合 for (Student s : hs) { System.out.println(s.getName() + "---" + s.getAge()); } } }
輸出結果:
王祖賢---30
范冰冰---22
林青霞---27
林青霞---20
柳岩---22
3)
HashSet集合的add()方法的源碼
問題:為什么存儲字符串的時候,字符串內容相同的只存儲了一個呢?
通過查看add方法的源碼,我們知道這個方法底層依賴 兩個方法:hashCode()和equals()。
步驟:
首先比較哈希值
如果相同,繼續走,比較地址值或者走equals()
如果不同,就直接添加到集合中
按照方法的步驟來說:
先看hashCode()值是否相同
相同:繼續走equals()方法
返回true: 說明元素重復,就不添加
返回false:說明元素不重復,就添加到集合
不同:就直接把元素添加到集合
如果類沒有重寫這兩個方法,默認使用的Object()。一般來說不會相同。
而String類重寫了hashCode()和equals()方法,所以,它就可以把內容相同的字符串去掉。只留下一個。
interface Collection { ... } interface Set extends Collection { ... } class HashSet implements Set { private static final Object PRESENT = new Object(); private transient HashMap<E,Object> map; public HashSet() { map = new HashMap<>(); } public boolean add(E e) { //e=hello,world return map.put(e, PRESENT)==null; } } class HashMap implements Map { public V put(K key, V value) { //key=e=hello,world //看哈希表是否為空,如果空,就開辟空間 if (table == EMPTY_TABLE) { inflateTable(threshold); } //判斷對象是否為null if (key == null) return putForNullKey(value); int hash = hash(key); //和對象的hashCode()方法相關 //在哈希表中查找hash值 int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { //這次的e其實是第一次的world Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; //走這里其實是沒有添加元素 } } modCount++; addEntry(hash, key, value, i); //把元素添加 return null; } transient int hashSeed = 0; final int hash(Object k) { //k=key=e=hello, int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); //這里調用的是對象的hashCode()方法 // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } } hs.add("hello"); hs.add("world"); hs.add("java"); hs.add("world");
4)HashSet存儲元素保證唯一性的代碼及圖解
5)Set集合練習:
HashSet集合存儲自定義對象並遍歷。如果對象的成員變量值相同即為同一個對象
自定義類Dog類:
package hashsetdemos; /** * Created by gao on 15-12-17. */ public class Dog { private String name; private int age; private String color; private char sex; public Dog() { } public Dog(String name, int age, String color, char sex) { this.name = name; this.age = age; this.color = color; this.sex = sex; } 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; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + ", color='" + color + '\'' + ", sex=" + sex + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Dog)) return false; Dog dog = (Dog) o; if (age != dog.age) return false; if (sex != dog.sex) return false; if (!color.equals(dog.color)) return false; if (!name.equals(dog.name)) return false; return true; } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + age; result = 31 * result + color.hashCode(); result = 31 * result + (int) sex; return result; } }
測試類:
package hashsetdemos; import java.util.HashSet; /** * Created by gao on 15-12-17. */ /* * HashSet集合存儲自定義對象並遍歷。如果對象的成員變量值相同即為同一個對象 * * 注意了: * 你使用的是HashSet集合,這個集合的底層是哈希表結構。 * 而哈希表結構底層依賴:hashCode()和equals()方法。 * 如果你認為對象的成員變量值相同即為同一個對象的話,你就應該重寫這兩個方法。 * 如何重寫呢?不同擔心,自動生成即可。 */ public class Exercise01 { public static void main(String[] args) { // 創建集合對象 HashSet<Dog> hs = new HashSet<Dog>(); // 創建狗對象 Dog d1 = new Dog("秦檜", 25, "紅色", '男'); Dog d2 = new Dog("高俅", 22, "黑色", '女'); Dog d3 = new Dog("秦檜", 25, "紅色", '男'); Dog d4 = new Dog("秦檜", 20, "紅色", '女'); Dog d5 = new Dog("魏忠賢", 28, "白色", '男'); Dog d6 = new Dog("李蓮英", 23, "黃色", '女'); Dog d7 = new Dog("李蓮英", 23, "黃色", '女'); Dog d8 = new Dog("李蓮英", 23, "黃色", '男'); //添加元素 hs.add(d1); hs.add(d2); hs.add(d3); hs.add(d4); hs.add(d5); hs.add(d6); hs.add(d7); hs.add(d8); //遍歷 for (Dog d : hs) { System.out.println(d.getName() + "---" + d.getAge() + "---" + d.getColor() + "---" + d.getSex()); } } }
輸出結果:
李蓮英---23---黃色---男
魏忠賢---28---白色---男
李蓮英---23---黃色---女
秦檜---25---紅色---男
秦檜---20---紅色---女
高俅---22---黑色---女
3、LinkedHashSet類
具有可預知迭代順序的 Set 接口的哈希表和鏈接列表實現。此實現與 HashSet 的不同之外在於,后者維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,即按照將元素插入到 set 中的順序(插入順序)進行迭代。注意,插入順序不 受在 set 中重新插入的 元素的影響。
LinkedHashSet:底層數據結構由哈希表和鏈表組成。
哈希表保證元素的唯一性。
鏈表保證元素有素。(存儲和取出是一致)
package linkedhashset; import java.util.LinkedHashSet; /** * Created by gao on 15-12-17. */ public class LinkedHashSetDemo { public static void main(String[] args) { // 創建集合對象 LinkedHashSet<String> hs = new LinkedHashSet<String>(); // 創建並添加元素 hs.add("hello"); hs.add("world"); hs.add("java"); hs.add("world"); hs.add("java"); // 遍歷 for (String s : hs) { System.out.println(s); } } }