什么是HashSet?操作過程是怎么樣的?
1、HashSet底層實際上是一個HashMap,HashMap底層采用了哈希表數據結構
2、哈希表又叫做散列表,哈希表底層是一個數組,這個數組中每一個元素是一個單向鏈表,每個單向鏈表都有一個獨一無二的hash值,代表數組的下標。在某個單向鏈表中的每一個節點上的hash值是相同的。hash值實際上是key調用hashCode方法,再通過"hash function"轉換成的值
3、如何向哈希表中添加元素?
先調用被存儲的key的hashCode方法,經過某個算法得出hash值,如果在這個哈希表中不存在這個hash值,則直接加入元素。如果該hash值已經存在,繼續 調用Key之間的equals方法,如果equals方法返回false,則將該元素添加。如果equals方法返回true,則放棄添加該元素
HashMap和HashSet初始化容量是16,默認加載因子是0.75
HashSet的數據結構
獻上我看視頻截的圖
代碼舉例
1 public class Test{ 2 3 public static void main(String[] args) { 4 5 Set set = new HashSet(); 6 7 Student stu1 = new Student("1", "JACK"); 8 Student stu2 = new Student("2", "TOM"); 9 Student stu3 = new Student("3", "JIM"); 10 11 set.add(stu1); 12 set.add(stu2); 13 set.add(stu3); 14 15 System.out.println("size :" + set.size()); 16 } 17 18 19 20 } 21 22 class Student{ 23 String no; 24 String name; 25 26 Student(String no, String name){ 27 this.no = no; 28 this.name = name; 29 } 30 }
這個的輸出結果顯而易見是3,因為我們添加了三個元素,但是如果改一下
1 Student stu1 = new Student("1", "JACK"); 2 Student stu2 = new Student("1", "JACK"); 3 Student stu3 = new Student("3", "JIM"); 4 5 System.out.println(stu1.hashCode()); 6 System.out.println(stu2.hashCode());
可以運行試一下,stu1和stu2的hashCode是不一樣的,為什么呢?因為這兩個對象是New出來的,引用地址不一樣。我們不希望出現這樣的情況,那就要重寫hashCode和equals方法
1 class Student{ 2 String no; 3 String name; 4 5 Student(String no, String name){ 6 this.no = no; 7 this.name = name; 8 } 9 10 public boolean equals(Object o){ 11 if(this == o) return true; 12 if(o instanceof Student){ 13 Student student = (Student) o; 14 if(student.no.equals(this.no) && student.name.equals(this.name)) return true; 15 } 16 17 return false; 18 } 19 20 public int hashCode(){ 21 return no.hashCode(); 22 } 23 }
再次運行,插入兩個數據一樣的對象,就不會重復了
TreeSet
treeset實現了sortedset接口,有個很重要的特點是里面的元素都是有序的
1 public class Test{ 2 3 public static void main(String[] args) { 4 5 SortedSet set = new TreeSet(); 6 7 set.add(1); 8 set.add(100); 9 set.add(50); 10 11 System.out.println(set); 12 } 13 14 }
輸出結果:[1, 50, 100]
那如果我們自定義類可以進行比較嗎?
1 public class Test{ 2 3 public static void main(String[] args) { 4 5 SortedSet set = new TreeSet(); 6 7 Student stu1 = new Student(22); 8 Student stu2 = new Student(11); 9 Student stu3 = new Student(100); 10 11 set.add(stu1); 12 set.add(stu2); 13 set.add(stu3); 14 15 System.out.println(set); 16 } 17 18 } 19 20 class Student{ 21 int age; 22 23 Student(int age){ 24 this.age = age; 25 } 26 }
這樣運行會出現一個問題,報ClassCastException,這就需要來看看源碼了,底層到底是怎么實現的,為什么自定義類就先不行呢?
這里可以看到,當我們在創建一個TreeSet的時候,實際上是new了一個TreeMap
add的時候調用了TreeMap的put方法,來看看TreeMap中的put方法
可以看到第三個紅框那里,會對key進行一個強制類型轉換,我們上面的代碼肯定是就是在這里裝換不成功,Comparable是什么?來看看API
是一個接口,從翻譯就可以看出,只要實現了這個接口,就是可以比較的,下面是實現了這個接口的類
隨便框了幾個,上面就有Integer,這個類實現了comparable接口,因此第一個代碼是正確的,現在我們是不是只要實現這個接口就好了呢!
1 class Student implements Comparable{ 2 int age; 3 4 Student(int age){ 5 this.age = age; 6 } 7 8 //重寫接口中的方法 9 //要重寫比較規則 10 @Override 11 public int compareTo(Object o) { 12 // TODO Auto-generated method stub 13 int age1 = this.age; 14 int age2 = ((Student)o).age; 15 return age2 - age1; 16 } 17 }
這樣OK了,再來看看上面有紅框的源碼,cmp<0,cmp>0,left,right都是什么東西,二叉樹!所以底層是通過二叉樹來排序的
如果是自定義類中的String呢,那就return String的compareTo方法就好了