Java中的HashSet、LinkedHashSet集合解析
Set系列集合
- List系列集合:添加的元素是有序、可重復(fù)、有索引
- Set系列集合:添加的元素是無序、不可重復(fù)、無索引
- 無序:存取順序不一致
- 不重復(fù):可以去除重復(fù)
- 無索引:沒有帶索引的方法,所以不能使用普通for循環(huán)遍歷,也不能通過索引來獲取元素
Set集合的實現(xiàn)類
- HashSet:無序、不重復(fù)、無索引
- LinkedHashSet:有序、不重復(fù)、無索引
- TreeSet:可排序、不重復(fù)、無索引
Set接口中的方法上基本上與Collection的API一致
Collection
Collection是單列集合的祖宗接口,它的功能是全部單列集合都可以繼承使用的
方法名稱 | 說明 |
public boolean add(E e) | 把給定的對象添加到當(dāng)前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把給定的對象在當(dāng)前集合中刪除 |
public boolean contains(Object obj) | 判斷當(dāng)前集合中是否包含給定的對象 |
public boolean isEmpty() | 判斷當(dāng)前集合是否為空 |
public int size() | 返回集合中元素的個數(shù)/集合長度 |
練習(xí):存儲字符串并遍歷
利用Set系列的集合,添加字符串,并使用多種方式遍歷
1。迭代器
2。增強for
3。Lambda表達(dá)式
public class A01_SetDemo1 { public static void main(String[] args) { //1.創(chuàng)建一個Set集合的對象 Set<String> s = new HashSet<>(); //2.添加元素 boolean r1 = s.add("朵朵"); boolean r2 = s.add("朵朵"); //true System.out.println(r1); //false System.out.println(r2); //[朵朵] System.out.println(s); boolean r3 = s.add("小七"); boolean r4 = s.add("鋼镚"); //無序 //[鋼镚, 朵朵, 小七] System.out.println(s); //迭代器遍歷 Iterator<String> it = s.iterator(); while (it.hasNext()) { String str = it.next(); System.out.println(str); } //增強for for (String str : s) { System.out.println(str); } //Lambda s.forEach(str -> System.out.println(str)); } }
HashSet
- HashSet集合底層采用哈希表存儲數(shù)據(jù)
- 哈希表是一種對與增刪改查數(shù)據(jù)性能都較好的結(jié)構(gòu)
哈希表組成
- JDK8之前:數(shù)組+鏈表
- JDK8之后:數(shù)組+鏈表+紅黑樹
哈希值
對象的整數(shù)表現(xiàn)形式
- 根據(jù)hashCode方法算出來的int類型的整數(shù)
- 該方法定義在Object類中,所有對象都可以調(diào)用,默認(rèn)使用地址值進(jìn)行計算
- 一般情況下,會重寫hashCode方法,利用對象內(nèi)部的屬性值計算哈希值
對象的哈希值特點
- 如果沒有重寫hashCode方法,不同對象計算出的哈希值是不同的
- 如果已經(jīng)重寫hashCode方法,不同的對象只要屬性值相同,計算出的哈希值就是一樣的
- 在小部分情況下,不同的屬性值或者不同地址值計算出來的哈希值也可能一樣(哈希碰撞)
public class A02_HashSetDemo1 { public static void main(String[] args) { //1。創(chuàng)建對象 Student s1 = new Student("朵朵", 5); Student s2 = new Student("朵朵", 5); //2。 如果沒有重寫hashCode方法,不同對象計算出的哈希值是不同的 //1554874502 System.out.println(s1.hashCode()); //1846274136 System.out.println(s2.hashCode()); //重寫hashCode后:不同的對象只要屬性值相同,計算出的哈希值就是一樣的 //26209637 System.out.println(s1.hashCode()); //26209637 System.out.println(s2.hashCode()); //3。在小部分情況下,不同的屬性值或者不同地址值計算出來的哈希值也可能一樣(哈希碰撞) //96354 System.out.println("abc".hashCode()); //96354 System.out.println("acD".hashCode()); } }
HashSet的底層原理
1.創(chuàng)建一個默認(rèn)長度16,默認(rèn)加載因子0.75的數(shù)組,數(shù)組名table
- 加載因子:擴容時機
- 當(dāng)存入數(shù)據(jù)達(dá)到16 x 0.75 = 12時候,數(shù)組擴容成原數(shù)組的二倍
- JKD8以后:當(dāng)鏈表長度大于8而且數(shù)組長度大于等于64,當(dāng)前鏈表自動轉(zhuǎn)成紅黑樹,增加查找效率
2.根據(jù)元素的哈希值跟數(shù)組的長度計算出應(yīng)存入的位置
- int index = (數(shù)組長度 - 1) & 哈希值;
- 如果集合中存儲的是自定義對象,必須要重寫hashCode和equsl方法
3.判斷當(dāng)前位置是否為null,如果是null直接存入(添加元素)
4.如果位置不是null,表示有元素,則調(diào)用equals方法比較屬性值
- 一樣:不存 ------ 不一樣:存入數(shù)組,形成鏈表
- JDK8以前:新元素存入數(shù)組,老元素掛在新元素下面
- JDK8以后:新元素掛在老元素下面
HashSet的三個問題
- 問題1.HashSet為什么存和取的順序不一樣?
- HashSet遍歷是從數(shù)組的0索引開始,一條鏈表一條鏈表(或紅黑樹)的遍歷,可能和存儲時候的順序不一樣
- 問題2.HashSet為什么沒有索引?
- HashSet不夠純粹,在底層是鏈表 數(shù)組 紅黑樹三種組合,定義誰都不合適。
- 假設(shè)使用數(shù)組的索引,但一個索引處可能會有鏈表,不可能鏈表上所有的數(shù)據(jù)都是一個索引
- 問題3.HashSet是利用什么機制保證數(shù)據(jù)去重復(fù)的
- HashCode方法 --> 哈希值 --> 確定元素添加在哪個位置
- equals方法 --> 比較內(nèi)部的屬性值是否相同(自定義對象要重寫這兩種方法)
練習(xí):利用HashSet集合去除重復(fù)元素
需求:創(chuàng)建一個存儲學(xué)生對象的集合,存儲多個學(xué)生對象 使用程序?qū)崿F(xiàn)在控制臺遍歷該集合
要求:學(xué)生對象的成員變量值相同,我們就認(rèn)為是同一個對象
public class A02_HashSetDemo2 { public static void main(String[] args) { //1。創(chuàng)建3個學(xué)生對象 Student s1 = new Student("朵朵", 5); Student s2 = new Student("小七", 7); Student s3 = new Student("鋼镚", 9); Student s4 = new Student("朵朵", 5); //2。創(chuàng)建集合用來添加學(xué)生 HashSet<Student> hs = new HashSet<>(); //3。添加元素(重寫hashCode和equal方法后) //true System.out.println(hs.add(s1)); //true System.out.println(hs.add(s2)); //true System.out.println(hs.add(s3)); //false System.out.println(hs.add(s4)); //4。打印集合 //[Student{name = 小七, age = 7}, Student{name = 鋼镚, age = 9}, Student{name = 朵朵, age = 5}] System.out.println(hs); } }
LinkedHashSet
LinkedHashSet底層原理
- 有序、不重復(fù)、無索引
- 這里的有序指的是保證存儲和取出的元素順序一致
- 原理:底層數(shù)據(jù)結(jié)構(gòu)依然是哈希表,只是每個元素又額外多了一個雙鏈表的機制記錄存儲的順序
public class A04_LinkedHashSetDemo { public static void main(String[] args) { //1。創(chuàng)建3個學(xué)生對象 Student s1 = new Student("朵朵", 5); Student s2 = new Student("小七", 7); Student s3 = new Student("鋼镚", 9); Student s4 = new Student("朵朵", 5); //2。創(chuàng)建集合用來添加學(xué)生 LinkedHashSet<Student> lhs = new LinkedHashSet<>(); //3。添加元素(重寫hashCode和equal方法后) //true System.out.println(lhs.add(s1)); //true System.out.println(lhs.add(s2)); //true System.out.println(lhs.add(s3)); //false System.out.println(lhs.add(s4)); //4。打印集合 //與添加順序一樣 //[Student{name = 朵朵, age = 5}, Student{name = 小七, age = 7}, Student{name = 鋼镚, age = 9}] System.out.println(lhs); } }
數(shù)據(jù)去重
- 默認(rèn)使用HashSet
- 如果要求去重且存取有序,才使用LinkedHashSet
到此這篇關(guān)于Java中的HashSet、LinkedHashSet集合解析的文章就介紹到這了,更多相關(guān)Java中的HashSet、LinkedHashSet內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring學(xué)習(xí)之開發(fā)環(huán)境搭建的詳細(xì)步驟
本篇文章主要介紹了Spring學(xué)習(xí)之開發(fā)環(huán)境搭建的詳細(xì)步驟,具有一定的參考價值,有興趣的可以了解一下2017-07-07Java?IO流之StringWriter和StringReader用法分析
這篇文章主要介紹了Java?IO流之StringWriter和StringReader用法分析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12Java中使用Hutool的DsFactory操作多數(shù)據(jù)源的實現(xiàn)
在Java開發(fā)中,管理多個數(shù)據(jù)源是一項常見需求,Hutool作為一個全能的Java工具類庫,提供了DsFactory工具,幫助開發(fā)者便捷地操作多數(shù)據(jù)源,感興趣的可以了解一下2024-09-09