vs 2008網(wǎng)站做安裝包怎么找推廣渠道
目錄
1. 常見的集合有哪些?
2. 線程安全的集合有哪些?線程不安全的呢?
3. Arraylist與 LinkedList 異同點?
4. ArrayList 與 Vector 區(qū)別?
5. Array 和 ArrayList 有什么區(qū)別?什么時候該應(yīng) Array而不是 ArrayList 呢?
6. HashMap的底層數(shù)據(jù)結(jié)構(gòu)是什么?
7. 解決hash沖突的辦法有哪些?HashMap用的哪種?
8. HashMap 中 key 的存儲索引是怎么計算的?
9. HashMap 的put方法流程?
10. 一般用什么作為HashMap的key?
11. 為什么在解決 hash 沖突的時候,不直接用紅黑樹?而選擇先用鏈表,再轉(zhuǎn)紅黑樹?
12. HashMap默認加載因子是多少?為什么是 0.75,不是 0.6 或者 0.8 ?
13. ConcurrentHashMap 和Hashtable的效率哪個更高?為什么?
14. Iterator 和 ListIterator 有什么區(qū)別?
15. Collection框架中實現(xiàn)比較要怎么做?
16. 為什么推薦使用更高效的ConcurrentHashMap類來替代HashTable
17. Java中ArrayList集合底層是怎么進行擴容的
18. CurrentHashMap
19. Java 容器都有哪些?
20. Collection 和 Collections 有什么區(qū)別?
21. List、Set、Map 之間的區(qū)別是什么?
22. HashMap 和 Hashtable 有什么區(qū)別?
23. 如何決定使用 HashMap 還是 TreeMap?
24. 說一下 HashMap 的實現(xiàn)原理?
25. 說一下 HashSet 的實現(xiàn)原理?
26. ArrayList 和 LinkedList 的區(qū)別是什么?
27. 如何實現(xiàn)數(shù)組和 List 之間的轉(zhuǎn)換?
28. ArrayList 和Vector 的區(qū)別是什么?
29. Array 和 ArrayList 有何區(qū)別?
30. 在 Queue 中 poll()和 remove()有什么區(qū)別?
31. 哪些集合類是線程安全的?
32. 迭代器 Iterator 是什么?
a. 迭代器可以遍歷Map集合嗎
33. Iterator 怎么使用?有什么特點?
34. Iterator 和 ListIterator 有什么區(qū)別?
35. 怎么確保一個集合不能被修改?
1. 常見的集合有哪些?
Java集合類主要由兩個根接口Collection和Map派生出來的,Collection派生出了三個子接口:List、Set、Queue(Java5新增的隊列),因此Java集合大致也可分成List、Set、Queue、Map四種接口體系。
注意:Collection是一個接口,Collections是一個工具類,Map不是Collection的子接口。
2. 線程安全的集合有哪些?線程不安全的呢?
線程安全的:
Hashtable:比HashMap多了個線程安全。
ConcurrentHashMap:是一種高效但是線程安全的集合。
Vector:比Arraylist多了個同步化機制。
Stack:棧,也是線程安全的,繼承于Vector。
線性不安全的:
HashMap
Arraylist
LinkedList
HashSet
TreeSet
TreeMap
3. Arraylist與 LinkedList 異同點?
是否保證線程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保證線程安全;
底層數(shù)據(jù)結(jié)構(gòu): Arraylist 底層使用的是Object數(shù)組;LinkedList 底層使用的是雙向循環(huán)鏈表
插入和刪除是否受元素位置的影響: ArrayList 采用數(shù)組存儲,所以插入和刪除元素的時間
復(fù)雜度受元素位置的影響。 比如:執(zhí)行 add(E e) 方法的時候, ArrayList 會默認在將指定的元
素追加到此列表的末尾,這種情況時間復(fù)雜度就是O(1)。但是如果要在指定位置 i 插入和刪除元素的話( add(int index, E element) )時間復(fù)雜度就為 O(n-i)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之后的(n-i)個元素都要執(zhí)行向后位/向前移一位的操作。
LinkedList 采用鏈表存儲,所以插入,刪除元素時間復(fù)雜度不受元素位置的影響,都是近似 O(1)而數(shù)組為近似 O(n)。
是否支持快速隨機訪問: LinkedList 不支持高效的隨機元素訪問,而ArrayList 實現(xiàn)了
RandmoAccess 接口,所以有隨機訪問功能??焖匐S機訪問就是通過元素的序號快速獲取元素對象(對應(yīng)于 get(int index) 方法)。
內(nèi)存空間占用: ArrayList的空 間浪費主要體現(xiàn)在在list列表的結(jié)尾會預(yù)留一定的容量空間,而
LinkedList的空間花費則體現(xiàn)在它的每一個元素都需要消耗比ArrayList更多的空間(因為要存放直接后繼和直接前驅(qū)以及數(shù)據(jù))。
4. ArrayList 與 Vector 區(qū)別?
Vector是線程安全的,ArrayList不是線程安全的。其中,Vector在關(guān)鍵性的方法前面都加了synchronized關(guān)鍵字,來保證線程的安全性。如果有多個線程會訪問到集合,那最好是使用Vector,因為不需要我們自己再去考慮和編寫線程安全的代碼。
ArrayList在底層數(shù)組不夠用時在原來的基礎(chǔ)上擴展0.5倍,Vector是擴展1倍,這樣ArrayList就有利于節(jié)約內(nèi)存空間。
5. Array 和 ArrayList 有什么區(qū)別?什么時候該應(yīng) Array而不是 ArrayList 呢?
Array 可以包含基本類型和對象類型,ArrayList 只能包含對象類型。
Array 大小是固定的,ArrayList 的大小是動態(tài)變化的。
ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。
6. HashMap的底層數(shù)據(jù)結(jié)構(gòu)是什么?
在JDK1.7 和JDK1.8 中有所差別: 在JDK1.7 中,由“數(shù)組+鏈表”組成,數(shù)組是 HashMap 的主體,鏈表則是主要為了解決哈希沖突而存在 的。 在JDK1.8 中,由“數(shù)組+鏈表+紅黑樹”組成。當(dāng)鏈表過長,則會嚴重影響 HashMap 的性能,紅黑樹搜索 時間復(fù)雜度是 O(logn),而鏈表是糟糕的 O(n)。因此,JDK1.8 對數(shù)據(jù)結(jié)構(gòu)做了進一步的優(yōu)化,引入了紅 黑樹,鏈表和紅黑樹在達到一定條件會進行轉(zhuǎn)換: 當(dāng)鏈表超過 8 且數(shù)組長度超過 64 才會轉(zhuǎn)紅黑樹。 將鏈表轉(zhuǎn)換成紅黑樹前會判斷,如果當(dāng)前數(shù)組的長度小于 64,那么會選擇先進行數(shù)組擴容,而不 是轉(zhuǎn)換為紅黑樹,以減少搜索時間。 紅黑樹相當(dāng)于排序數(shù)據(jù),可以自動的使用二分法進行定位,性能較高。一般情況下,hash值做的比較好的話基本上用不到紅黑樹。
7. 解決hash沖突的辦法有哪些?HashMap用的哪種?
解決Hash沖突方法有:開放定址法、再哈希法、鏈地址法(拉鏈法)、建立公共溢出區(qū)。HashMap中采用的是 鏈地址法 。
開放定址法也稱為 再散列法 ,基本思想就是,如果 p=H(key) 出現(xiàn)沖突時,則以 p 為基礎(chǔ),再次hash, p1=H(p) ,如果p1再次出現(xiàn)沖突,則以p1為基礎(chǔ),以此類推,直到找到一個不沖突的哈希地址 pi 。 因此開放定址法所需要的hash表的長度要大于等于所需要存放的元素,而且因為存在再次hash,所以 只能在刪除的節(jié)點上做標(biāo)記,而不能真正刪除節(jié)點。
再哈希法(雙重散列,多重散列),提供多個不同的hash函數(shù),當(dāng) R1=H1(key1) 發(fā)生沖突時,再計算 R2=H2(key1) ,直到?jīng)]有沖突為止。 這樣做雖然不易產(chǎn)生堆集,但增加了計算的時間。
鏈地址法(拉鏈法),將哈希值相同的元素構(gòu)成一個同義詞的單鏈表,并將單鏈表的頭指針存放在哈希表的第i個單元中,查找、插入和刪除主要在同義詞鏈表中進行。鏈表法適用于經(jīng)常進行插入和刪除的情況。
建立公共溢出區(qū),將哈希表分為公共表和溢出表,當(dāng)溢出發(fā)生時,將所有溢出數(shù)據(jù)統(tǒng)一放到溢出區(qū)。
8. HashMap 中 key 的存儲索引是怎么計算的?
- 首先,HashMap會調(diào)用key對象的hashCode()方法來獲取一個哈希碼(hash code)。哈希碼是一個整數(shù)值,一般是根據(jù)key對象的內(nèi)容計算得出的。
- 然后,HashMap會對哈希碼進行一系列的運算,以確定key在HashMap中的存儲位置。其中,一個關(guān)鍵的操作是對哈希碼進行取模運算,將其映射到HashMap的桶數(shù)組(bucket array)中。
- 桶數(shù)組是HashMap內(nèi)部用于存儲鍵值對的數(shù)組結(jié)構(gòu)。每個桶(bucket)實際上是一個鏈表或者紅黑樹,用于處理哈希沖突(即不同的key計算得到相同的哈希碼)。
- 最終,HashMap根據(jù)取模運算的結(jié)果,將key存儲在相應(yīng)的桶中。
9. HashMap 的put方法流程?
以JDK1.8為例,簡要流程如下:
首先根據(jù) key 的值計算 hash 值,找到該元素在數(shù)組中存儲的下標(biāo);
如果數(shù)組是空的,則調(diào)用 resize 進行初始化;
如果沒有哈希沖突直接放在對應(yīng)的數(shù)組下標(biāo)里;
如果沖突了,且 key 已經(jīng)存在,就覆蓋掉 value;
如果沖突后,發(fā)現(xiàn)該節(jié)點是紅黑樹,就將這個節(jié)點掛在樹上;
如果沖突后是鏈表,判斷該鏈表是否大于 8 ,如果大于 8 并且數(shù)組容量小于 64,就進行擴容;
如果鏈表節(jié)點大于 8 并且數(shù)組的容量大于 64,則將這個結(jié)構(gòu)轉(zhuǎn)換為紅黑樹;否則,鏈表插入鍵值對,若 key 存在,就覆蓋掉 value。
10. 一般用什么作為HashMap的key?
一般用Integer、String 這種不可變類當(dāng) HashMap 當(dāng) key,而且 String 最為常用。
因為字符串是不可變的,所以在它創(chuàng)建的時候 hashcode 就被緩存了,不需要重新計算。這就是HashMap 中的鍵往往都使用字符串的原因。
因為獲取對象的時候要用到 equals() 和 hashCode() 方法,那么鍵對象正確的重寫這兩個方法是非常重要的,這些類已經(jīng)很規(guī)范的重寫了 hashCode() 以及 equals() 方法。
11. 為什么在解決 hash 沖突的時候,不直接用紅黑樹?而選擇先用鏈表,再轉(zhuǎn)紅黑樹?
因為紅黑樹需要進行左旋,右旋,變色這些操作來保持平衡,而單鏈表不需要。當(dāng)元素小于 8 個的時候,此時做查詢操作,鏈表結(jié)構(gòu)已經(jīng)能保證查詢性能。當(dāng)元素大于 8 個的時候, 紅黑樹搜索時間復(fù)雜度是 O(logn),而鏈表是 O(n),此時需要紅黑樹來加快查詢速度,但是新增節(jié)點的效率變慢了。
因此,如果一開始就用紅黑樹結(jié)構(gòu),元素太少,新增效率又比較慢,無疑這是浪費性能的。
12. HashMap默認加載因子是多少?為什么是 0.75,不是 0.6 或者 0.8 ?
Node[] table的初始化長度length(默認值是16),Load factor為負載因子(默認值是0.75),threshold是 HashMap所能容納鍵值對的最大值。threshold = length * Load factor。也就是說,在數(shù)組定義好長度 之后,負載因子越大,所能容納的鍵值對個數(shù)越多。 默認的loadFactor是0.75,0.75是對空間和時間效率的一個平衡選擇,一般不要修改,除非在時間和空間比較特殊的情況下 :
如果內(nèi)存空間很多而又對時間效率要求很高,可以降低負載因子Load factor的值 。
相反,如果內(nèi)存空間緊張而對時間效率要求不高,可以增加負載因子loadFactor的值,這個值可以大1。
13. ConcurrentHashMap 和Hashtable的效率哪個更高?為什么?
ConcurrentHashMap 的效率要高于Hashtable,因為Hashtable給整個哈希表加了一把大鎖從而實現(xiàn)線程安全。而ConcurrentHashMap 的鎖粒度更低,在JDK1.7中采用分段鎖實現(xiàn)線程安全,在JDK1.8 中采用 CAS+Synchronized 實現(xiàn)線程安全。
- Java 7:Java 7中的ConcurrentHashMap使用了分段鎖(Segment Locking)機制。它將整個哈希表分為多個段(Segments),每個段都有自己的鎖。這樣可以在多線程訪問時,同一時間允許多個線程在不同的段上進行并發(fā)的讀寫操作。這種機制在降低鎖競爭的同時,提高了并發(fā)性能。
- Java 8:Java 8對ConcurrentHashMap進行了進一步的改進,引入了基于CAS(Compare and Swap)操作的無鎖更新機制。在Java 8中,ConcurrentHashMap使用了一種叫做"鎖條目"(Lock Striping)的機制。它將整個哈希表分為多個較小的桶(Buckets),每個桶都可以獨立進行并發(fā)的讀寫操作,每個桶內(nèi)部使用CAS操作和volatile變量來進行同步,避免了鎖的開銷,并提高了并發(fā)性能。
- Java 9:Java 9在ConcurrentHashMap的鎖機制上進行了一些改進。它引入了一種叫做"分層鎖"(Hierarchy of Locks)的機制,通過將對鎖的競爭限制在特定的層級上來提高并發(fā)性能。該機制為并發(fā)的讀取和寫入操作提供了更好的擴展性和吞吐量。
14. Iterator 和 ListIterator 有什么區(qū)別?
遍歷。使用Iterator,可以遍歷所有集合,如Map,List,Set;但只能在向前方向上遍歷集合中的元素。
使用ListIterator,只能遍歷List實現(xiàn)的對象,但可以向前和向后遍歷集合中的元素。
添加元素。Iterator無法向集合中添加元素;而,ListIteror可以向集合添加元素。
修改元素。Iterator無法修改集合中的元素;而,ListIterator可以使用set()修改集合中的元素。
索引。Iterator無法獲取集合中元素的索引;而,使用ListIterator,可以獲取集合中元素的索引。
15. Collection框架中實現(xiàn)比較要怎么做?
第一種,實體類實現(xiàn)Comparable接口,并實現(xiàn) compareTo(T t) 方法,稱為內(nèi)部比較器。
第二種,創(chuàng)建一個外部比較器,這個外部比較器要實現(xiàn)Comparator接口的 compare(T t1, T t2)方法
16. 為什么推薦使用更高效的ConcurrentHashMap類來替代HashTable
- 性能:ConcurrentHashMap相對于HashTable在多線程環(huán)境下具有更好的性能。ConcurrentHashMap實現(xiàn)了細粒度的鎖機制,通過將數(shù)據(jù)分割為多個段(Segment)來實現(xiàn)并發(fā)訪問。這樣可以使多個線程在訪問不同的段時并行進行,減少了線程之間的競爭,提高了并發(fā)性能。而HashTable則使用了全局的鎖,當(dāng)一個線程訪問HashTable時,其他線程需要等待,導(dǎo)致并發(fā)性能相對較低。
- 擴展性:ConcurrentHashMap允許多個線程同時讀取和修改數(shù)據(jù),而不會阻塞其他線程的訪問。這使得ConcurrentHashMap在高并發(fā)場景下能夠更好地擴展,提供更好的吞吐量和響應(yīng)性。
- 功能:ConcurrentHashMap提供了更多的功能和靈活性。例如,可以使用Iterator來遍歷ConcurrentHashMap的結(jié)果,在遍歷過程中允許其他線程對Map進行修改。此外,ConcurrentHashMap還提供了更多的操作方法,如putIfAbsent()、replace()等等,使得更復(fù)雜的并發(fā)操作變得更加方便。
17. Java中ArrayList集合底層是怎么進行擴容的
- 初始化容量:當(dāng)創(chuàng)建一個 ArrayList 對象時,會為其分配一個初始容量。默認情況下,初始容量是 10,但也可以使用指定初始容量的構(gòu)造函數(shù)來創(chuàng)建 ArrayList。
- 添加元素:當(dāng)向 ArrayList 中添加元素時,如果當(dāng)前元素個數(shù)(size)達到了容量(capacity),即數(shù)組已滿,就需要進行擴容。
- 計算新容量:擴容時,ArrayList 會計算新的容量大小。一般情況下,新容量是原容量的 1.5 倍。例如,當(dāng)前容量為 10,那么新容量就是 15。
- 創(chuàng)建新數(shù)組:根據(jù)計算得到的新容量,ArrayList 會創(chuàng)建一個新的數(shù)組。
- 復(fù)制元素:接下來,ArrayList 使用系統(tǒng)提供的 System.arraycopy() 方法,將原數(shù)組中的元素逐個復(fù)制到新的數(shù)組中,保持元素的順序和索引不變。這個過程會遍歷原數(shù)組,然后將元素逐個拷貝到新數(shù)組的相同位置。
- 更新引用:復(fù)制完成后,ArrayList 會更新內(nèi)部的數(shù)組引用,指向新的數(shù)組。
- 擴容完成:現(xiàn)在,ArrayList 的底層數(shù)組已經(jīng)擴容成功,并可以接受新的元素添加。
18. CurrentHashMap
基本結(jié)構(gòu):
分布策略: 數(shù)組長度始終保持為2的次冪,講哈希值的高位參與運算,通過為與操作來等價取模操作
動態(tài)擴容:
1.7版本:采用分段鎖,維護了一個Segment數(shù)組,數(shù)組的元素是HashEntry數(shù)組
數(shù)組中的數(shù)據(jù)就依賴同一把鎖,不同HashEntry數(shù)組的讀寫數(shù)據(jù)互不干擾,就形成了分段鎖
put方法
19. Java 容器都有哪些?
- ArrayList: 動態(tài)數(shù)組,可以根據(jù)需要調(diào)整大小。
- LinkedList: 雙向鏈表,適合插入和刪除操作。
- HashSet: 無序集合,使用哈希表實現(xiàn),不允許重復(fù)元素。
- TreeSet: 有序集合,基于紅黑樹實現(xiàn),可以自動排序元素。
- LinkedHashSet: 有序集合,使用哈希表和鏈表維護元素的插入順序。
- HashMap: 無序鍵值對集合,使用哈希表實現(xiàn),鍵和值都可以為null。
- TreeMap: 有序鍵值對集合,基于紅黑樹實現(xiàn),按鍵進行排序。
- LinkedHashMap: 有序鍵值對集合,使用哈希表和鏈表維護插入順序。
- Stack: 堆棧,遵循先進后出的原則。
- Queue: 隊列,遵循先進先出的原則。
- PriorityQueue: 優(yōu)先級隊列,按照元素的優(yōu)先級進行排序。
除了上述常見的容器類型之外,Java還提供了其他特定用途的容器,例如:
- Hashtable: 類似于HashMap,但是是線程安全的。
- Vector: 動態(tài)數(shù)組,類似于ArrayList,但是是線程安全的。
- ConcurrentHashMap: 與HashMap類似,但是是線程安全的。
- ConcurrentLinkedQueue: 無界隊列,線程安全。
20. Collection 和 Collections 有什么區(qū)別?
- Collection:Collection 是 Java 中定義集合類的接口。它是所有集合類的父接口,定義了集合類的基本操作和行為。Collection 接口繼承自 Iterable 接口,提供了對集合元素的基本操作,如添加、刪除、遍歷等。一些常見的實現(xiàn)類包括 List、Set 和 Queue。
- Collections:Collections 是 Java 中的一個工具類,提供了一系列靜態(tài)方法,用于操作集合類的工具方法。它包含了一些靜態(tài)方法,用于對集合進行排序、搜索、同步等操作。這些方法可以用于操作任何實現(xiàn)了 Collection 接口的集合,如 List、Set 等。Collections 類中的方法是通過靜態(tài)調(diào)用的方式來使用的,常用的一些方法包括 sort()、binarySearch()、synchronizedCollection() 等。
21. List、Set、Map 之間的區(qū)別是什么?
List、Set和Map是Java集合框架中三種常用的接口,它們具有不同的特點和用途:
- List(列表):
- 允許重復(fù)元素。
- 保留了元素的插入順序。
- 可以根據(jù)索引訪問元素。
- 常見的實現(xiàn)類有ArrayList(基于數(shù)組實現(xiàn)),LinkedList(基于鏈表實現(xiàn))。
- Set(集合):
- 不允許重復(fù)元素,保證元素的唯一性。
- 不保留元素的插入順序。
- 不提供根據(jù)索引訪問元素的方式。
- 常見的實現(xiàn)類有HashSet(基于哈希表實現(xiàn)),TreeSet(基于紅黑樹實現(xiàn))。
- Map(映射):
- 存儲鍵值對(Key-Value)的集合。
- 不允許重復(fù)的鍵,但允許值的重復(fù)。
- 鍵是唯一的,通過鍵可以獲取對應(yīng)的值。
- 常見的實現(xiàn)類有HashMap(基于哈希表實現(xiàn)),TreeMap(基于紅黑樹實現(xiàn)),LinkedHashMap(基于鏈表和哈希表實現(xiàn))。
總結(jié):
- List適合需要按照插入順序存儲和訪問元素的場景,允許重復(fù)元素。
- Set適合需要確保元素唯一性的場景,不關(guān)心元素的順序。
- Map適合需要存儲鍵值對,并且需要通過唯一鍵快速查找對應(yīng)值的場景,鍵是唯一的,值可以重復(fù)。
22. HashMap 和 Hashtable 有什么區(qū)別?
HashMap和Hashtable是Java中兩種常用的鍵值對存儲容器,它們具有一些區(qū)別和不同的特點:
- 線程安全性:
- HashMap是非線程安全的,不對多線程并發(fā)操作進行同步處理。如果在多線程環(huán)境下使用HashMap,需要自行處理線程安全問題。
- Hashtable是線程安全的,對多線程并發(fā)進行同步處理。它的方法都是同步的,適合在多線程環(huán)境下使用。
- 允許鍵或值為null:
- HashMap允許鍵和值為null,即可以使用null作為鍵或值進行存儲。
- Hashtable不允許鍵或值為null,如果嘗試使用null,將會拋出NullPointerException。
- 繼承關(guān)系:
- HashMap繼承自AbstractMap類,實現(xiàn)了Map接口。
- Hashtable繼承自Dictionary類,實現(xiàn)了Map接口。Dictionary類是一個已經(jīng)過時的類,不推薦使用。
- 迭代器:
- HashMap的迭代器(Iterator)是fail-fast的,在迭代過程中如果其他線程對HashMap進行了修改,將會拋出ConcurrentModificationException異常。
- Hashtable的迭代器是不fail-fast的,在迭代過程中如果其他線程對Hashtable進行修改,不會拋出異常,但可能會導(dǎo)致迭代結(jié)果不確定。
- 性能:
- 由于Hashtable在多線程環(huán)境下加鎖同步,會引入一定的性能開銷。
- HashMap不進行同步,較Hashtable在多線程環(huán)境下通常具有更好的性能。
需要注意的是,從Java 1.2版本開始,推薦使用HashMap而不是Hashtable,因為HashMap在大多數(shù)情況下具有更好的性能和靈活性。對于線程安全的需求,可以使用ConcurrentHashMap等并發(fā)集合類來替代Hashtable。
"Fail-fast"是一種軟件開發(fā)中的設(shè)計理念和編程模式,用于在程序出現(xiàn)并發(fā)修改異常時盡早地檢測并拋出異常ConcurrentModificationException,以避免潛在的不一致狀態(tài)。它主要應(yīng)用于迭代器和集合類的設(shè)計中。
23. 如何決定使用 HashMap 還是 TreeMap?
- 排序需求:TreeMap是基于紅黑樹實現(xiàn)的,可以保持鍵的有序性,按鍵的自然順序或自定義比較器進行排序。如果需要按照鍵的有序性進行遍歷或檢索,可以選擇TreeMap。HashMap則不保證鍵的有序性。
- 插入和查找頻率:HashMap在插入和查找操作上的性能通常優(yōu)于TreeMap。HashMap使用哈希表實現(xiàn),具有常數(shù)時間復(fù)雜度的查找和插入操作。而TreeMap的插入和查找操作的時間復(fù)雜度為O(log N),其中N是元素的數(shù)量。如果對于插入和查找的性能要求較高且不關(guān)心元素順序,可以選擇HashMap。
- 鍵的唯一性:HashMap允許鍵為null,并允許鍵的重復(fù)。而TreeMap要求鍵不能為null,并且會根據(jù)鍵的自然順序(或自定義比較器)來確保鍵的唯一性。如果需要保持鍵的唯一性,可以選擇TreeMap。
- 內(nèi)存占用:TreeMap相對于HashMap更加復(fù)雜,需要額外的紅黑樹結(jié)構(gòu)來維護鍵的有序性,可能會導(dǎo)致更高的內(nèi)存占用。如果對于內(nèi)存占用有限制,可以選擇HashMap。
綜上所述,一般情況下如果只需要快速的插入、查找或刪除操作,并且不關(guān)心元素順序,HashMap是更好的選擇。而如果需要基于鍵的順序進行遍歷、范圍查找或者保持鍵的唯一性,可以選擇TreeMap
24. 說一下 HashMap 的實現(xiàn)原理?
HashMap 是 Java 中最常用的數(shù)據(jù)結(jié)構(gòu)之一,它基于哈希表(Hash Table)實現(xiàn)。以下是 HashMap 的主要實現(xiàn)原理:
- 數(shù)據(jù)結(jié)構(gòu):HashMap 內(nèi)部使用數(shù)組和鏈表(或紅黑樹,Java 8+)來存儲數(shù)據(jù)。
數(shù)組默認長度為16,負載因子為0.75
- 哈希函數(shù):當(dāng)調(diào)用 put(key, value) 方法時,HashMap 首先會根據(jù) key 的哈希碼(通過 key 的 hashCode() 方法獲取)計算出桶索引。依靠哈希函數(shù),盡可能均勻地將鍵值對分布到桶中。
- 解決哈希碰撞:由于不同的 key 可能產(chǎn)生相同的哈希碼,所以可能出現(xiàn)哈希碰撞(多個鍵值對存儲在同一個桶中)。為了解決哈希碰撞,HashMap 采用鏈表或紅黑樹來存儲相同哈希碼的鍵值對。在 Java 8+ 版本中,當(dāng)鏈表中的元素數(shù)量大于一定閾值時,鏈表會轉(zhuǎn)換為紅黑樹,提高查詢效率。
- 查找元素:當(dāng)調(diào)用 get(key) 方法時,HashMap 根據(jù) key 的哈希碼定位到對應(yīng)的桶,然后在該桶中遍歷鏈表或紅黑樹,根據(jù) key 的 equals() 方法找到目標(biāo)鍵值對,并返回對應(yīng)的值。
- 擴容:當(dāng) HashMap 中的鍵值對數(shù)量超過負載因子(默認為 0.75)與桶數(shù)組容量的乘積時,會觸發(fā)擴容操作。擴容會創(chuàng)建一個更大容量的新數(shù)組,并重新計算鍵值對的哈希碼,然后重新分配至新的桶中,以提高散列性能。
- 迭代順序:HashMap 中的元素是無序的,遍歷的順序并不是插入的順序。如果需要有序遍歷,可以使用 LinkedHashMap。
需要注意的是,HashMap 是非線程安全的,如果在多線程環(huán)境下使用,建議使用線程安全的 ConcurrentHashMap 或進行適當(dāng)?shù)耐讲僮鳌?/p>
put() 首先會創(chuàng)建一個Entry對象,用來存儲鍵和值,根據(jù)鍵的哈希值計算在數(shù)組中的位置,如果位置為null,則直接添加進去,如果不為null,調(diào)用equals通過遍歷鏈表比較鍵是否相等,如果相等,則覆蓋元素,如果不等,則會在鏈表后面添加節(jié)點,1.8開始當(dāng)鏈表長度超過8,且數(shù)組長度大于64,會轉(zhuǎn)換成紅黑樹。
如果存儲的是自定義對象,需要重寫equals和hashcode方法
LinkedHashMap 有序,因為底層維護了一個雙向鏈表
25. 說一下 HashSet 的實現(xiàn)原理?
HashSet 是 Java 中常用的集合類,它實現(xiàn)了 Set 接口,并基于 HashMap 進行實現(xiàn)。HashSet 的實現(xiàn)原理如下:
- 內(nèi)部使用 HashMap:HashSet 內(nèi)部使用 HashMap 來存儲元素。HashMap 的鍵值對中,鍵表示 HashSet 中的元素,值固定為一個常量對象。
- 數(shù)據(jù)結(jié)構(gòu):HashSet 由 HashMap 實現(xiàn),實際上是一個 HashMap 的封裝。HashSet 內(nèi)部維護了一個 HashMap 對象,所有的元素都存儲為 HashMap 中的鍵,而值對象則是一個隨意選擇的常量。
- 不允許重復(fù)元素:HashSet 是基于HashMap 的鍵唯一性特性實現(xiàn)的。當(dāng)向 HashSet 中添加元素時,它會調(diào)用 HashMap 的 put() 方法,并使用該元素作為鍵來添加到 HashMap 中。由于 HashMap 的鍵是唯一的,所以實現(xiàn)了元素在 HashSet 中的唯一性。
- 哈希碼和 equals 比較:當(dāng)調(diào)用 HashSet 的 add() 方法時,HashSet 會獲取元素的哈希碼(通過元素的 hashCode() 方法)并計算桶索引。然后,它會在對應(yīng)桶位置上的鏈表或紅黑樹中查找是否存在相同哈希碼的元素。如果存在,則通過元素的 equals() 方法進行值比較,如果相等則不添加到集合中。
- 允許空元素:HashSet 允許存儲 null 元素。在 HashMap 的實現(xiàn)中,可以將 null 作為鍵存儲在 HashMap 中。
- 迭代順序:HashSet 中的元素是無序的,遍歷的順序并不是插入的順序。如果需要有序遍歷,可以考慮使用 LinkedHashSet。
需要注意的是,HashSet 是非線程安全的,如果在多線程環(huán)境下使用,建議使用線程安全的集合類,如 ConcurrentHashSet 或進行適當(dāng)?shù)耐讲僮鳌?/p>
HashSet 的實現(xiàn)原理使得它具有了快速的元素查找和插入的特性,同時具備自動去重的功能。在平均情況下,HashSet 的插入和查找操作的時間復(fù)雜度都近似于 O(1)。
26. ArrayList 和 LinkedList 的區(qū)別是什么?
- 內(nèi)部數(shù)據(jù)結(jié)構(gòu):
-
- ArrayList:內(nèi)部使用動態(tài)數(shù)組(數(shù)組長度可變)來存儲元素。它按索引進行訪問和修改元素效率較高,但在插入和刪除元素時需要移動其他元素。
- LinkedList:內(nèi)部使用雙向鏈表來存儲元素。每個元素包含存儲的值和指向前一個和后一個元素的指針。在插入和刪除元素時,僅需調(diào)整相鄰元素的指針,效率較高,但隨機訪問元素效率較低。
- 訪問效率:
-
- ArrayList:由于使用連續(xù)的內(nèi)存空間存儲元素,支持根據(jù)索引訪問元素,所以在隨機訪問時效率較高,時間復(fù)雜度為O(1)。但在插入和刪除元素時,需要移動其他元素,時間復(fù)雜度為O(n)。
- LinkedList:由于使用鏈表存儲元素,訪問元素時需要從頭節(jié)點或尾節(jié)點開始遍歷鏈表,時間復(fù)雜度為O(n)。但在插入和刪除元素時,僅需調(diào)整相鄰節(jié)點的指針,時間復(fù)雜度為O(1)。
- 內(nèi)存消耗:
-
- ArrayList:由于使用動態(tài)數(shù)組,每個元素連續(xù)存放在內(nèi)存中,不需要額外的指針存儲,因此相對節(jié)省內(nèi)存空間。
- LinkedList:由于使用鏈表,每個元素需要額外的指針來存儲前后關(guān)系,因此相對消耗更多的內(nèi)存空間。
- 適用場景:
-
- ArrayList:適用于需要頻繁通過索引進行訪問和修改元素的場景,例如按索引查找、更新、刪除等操作。
- LinkedList:適用于頻繁進行插入、刪除或只需要在鏈表的首尾進行操作的場景,例如隊列、棧等。
27. 如何實現(xiàn)數(shù)組和 List 之間的轉(zhuǎn)換?
要實現(xiàn)數(shù)組和List之間的轉(zhuǎn)換,可以使用Java中提供的相應(yīng)方法和構(gòu)造函數(shù)進行轉(zhuǎn)換。下面我將展示如何從數(shù)組轉(zhuǎn)換為List以及如何從List轉(zhuǎn)換為數(shù)組。
- 從數(shù)組轉(zhuǎn)換為List:
-
- 使用Arrays類的.asList()方法可以將數(shù)組轉(zhuǎn)換為List。這種轉(zhuǎn)換操作是基于底層數(shù)組的,因此對數(shù)組或轉(zhuǎn)換后的List的修改會相互影響。
- 示例代碼如下:
// 假設(shè)有一個整型數(shù)組
int[] array = {1, 2, 3, 4, 5};// 將數(shù)組轉(zhuǎn)換為List
List<Integer> list = Arrays.asList(array);
- 從List轉(zhuǎn)換為數(shù)組:
-
- 使用ArrayList的toArray()方法可以將List轉(zhuǎn)換為數(shù)組。
- 示例代碼如下:
// 假設(shè)有一個整型List
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);// 將List轉(zhuǎn)換為數(shù)組
Integer[] array = list.toArray(new Integer[list.size()]);
注意,在將List轉(zhuǎn)換為數(shù)組時,需要使用toArray()方法,并傳入一個具有相同類型的數(shù)組作為參數(shù)。如果不傳入?yún)?shù),toArray()方法將返回一個Object類型的數(shù)組。
另外,需要注意的是,通過上述轉(zhuǎn)換方法得到的List或數(shù)組是固定長度的,無法進行添加或刪除元素的操作。如果需要對List進行修改操作,可以創(chuàng)建一個新的ArrayList,并將轉(zhuǎn)換后的List的元素添加到新的ArrayList中。
28. ArrayList 和Vector 的區(qū)別是什么?
ArrayList和Vector是Java集合框架中的兩個常見的List實現(xiàn)類,它們在以下幾個方面有一些區(qū)別:
- 線程安全性:
-
- ArrayList:ArrayList是非線程安全的,在多線程環(huán)境下使用時需要手動進行同步或使用其他線程安全的技術(shù)進行保護。
- Vector:Vector是線程安全的,它的方法都使用了synchronized關(guān)鍵字進行同步。這意味著Vector適用于多線程環(huán)境下的并發(fā)訪問。
- 擴容機制:
-
- ArrayList:ArrayList在擴容時會自動增加容量。當(dāng)容量不足時,會創(chuàng)建一個更大的數(shù)組,并將原數(shù)組的元素復(fù)制到新數(shù)組中,擴容的增長因子是1.5。這可能會引起一定的性能損耗。
- Vector:Vector在擴容時也會自動增加容量。當(dāng)容量不足時,會創(chuàng)建一個更大的數(shù)組,并將原數(shù)組的元素復(fù)制到新數(shù)組中,擴容的增長因子是2。與ArrayList不同,Vector的擴容機制相對比較保守。
- 初始容量增長:
-
- ArrayList:ArrayList的默認初始容量為10,當(dāng)添加元素時,如果容量不足,會進行自動擴容。
- Vector:Vector的默認初始容量也為10,當(dāng)添加元素時,如果容量不足,會進行自動擴容。
- 效率:
-
- ArrayList:由于ArrayList不進行線程同步處理,相比Vector的效率要高一些。因此,如果在單線程環(huán)境下使用,ArrayList是更好的選擇。
- Vector:由于Vector使用了同步機制,導(dǎo)致額外的開銷,因此在單線程環(huán)境下,Vector的效率相對較低。
總的來說,ArrayList和Vector在功能上是相似的,都實現(xiàn)了List接口,但Vector是線程安全的并具有較為保守的擴容機制,適用于多線程的并發(fā)環(huán)境。而在單線程環(huán)境下,ArrayList由于沒有同步的開銷,可能會有更好的性能。根據(jù)具體的需求以及程序的并發(fā)情況,可以選擇合適的集合類來使用。
29. Array 和 ArrayList 有何區(qū)別?
Array和ArrayList是Java中用于存儲和操作一組元素的兩種不同的數(shù)據(jù)結(jié)構(gòu),它們具有以下區(qū)別:
- 大小固定性:
-
- Array:數(shù)組的大小是固定的,一旦創(chuàng)建后,大小就不能改變。
- ArrayList:ArrayList的大小是可變的,可以根據(jù)需要動態(tài)地添加或刪除元素。
- 類型:
-
- Array:數(shù)組可以存儲基本數(shù)據(jù)類型(如int,char等)和對象類型(如String,自定義類等)的元素。
- ArrayList:ArrayList只能存儲對象類型(Object)的元素,不能直接存儲基本數(shù)據(jù)類型,需要使用對應(yīng)的包裝類(如Integer,Character等)。
- 功能:
-
- Array:數(shù)組提供了與索引直接關(guān)聯(lián)的快速訪問元素的能力,可以根據(jù)索引進行讀取、修改和刪除元素。但沒有提供內(nèi)置的支持添加和刪除元素的方法,需要手動調(diào)整數(shù)組的大小和元素的位置。
- ArrayList:ArrayList提供了豐富的方法來添加、刪除和修改元素,而且內(nèi)部自動處理元素的增長和縮減。可以通過索引訪問元素,也可以使用諸如add()、remove()、set()等方法進行操作。
- 內(nèi)存管理:
-
- Array:數(shù)組在內(nèi)存中是連續(xù)存儲的,占用一塊固定大小的內(nèi)存空間。
- ArrayList:ArrayList在內(nèi)存中使用動態(tài)數(shù)組實現(xiàn),根據(jù)需要可以自動調(diào)整內(nèi)部數(shù)組的大小,適時擴容或縮減。因此,ArrayList可以根據(jù)元素數(shù)量的變化來使用有效的內(nèi)存。
綜上所述,Array是一種固定大小的數(shù)組,支持快速訪問元素,但不提供動態(tài)添加和刪除元素的方法。ArrayList是一個動態(tài)大小的數(shù)組,提供了豐富的方法來操作元素,適用于需要動態(tài)調(diào)整大小并進行增刪操作的場景。選擇使用哪種數(shù)據(jù)結(jié)構(gòu)取決于具體的需求和操作模式
30. 在 Queue 中 poll()和 remove()有什么區(qū)別?
在Queue接口中,poll()和remove()都是用于移除隊列中的元素的方法,它們在移除失敗時的行為稍有不同:
- poll()方法:
-
- 如果隊列為空,poll()方法返回null。
- 如果隊列不為空,poll()方法會移除并返回隊列頭部的元素。
- remove()方法:
-
- 如果隊列為空,remove()方法會拋出NoSuchElementException異常。
- 如果隊列不為空,remove()方法會移除并返回隊列頭部的元素。
因此,主要區(qū)別在于當(dāng)隊列為空時的行為不同。
使用poll()方法可以在隊列為空時返回null,而不會拋出異常。這種情況下,我們可以根據(jù)返回值是否為null來判斷是否成功移除了元素。
使用remove()方法則需要注意,如果隊列為空,調(diào)用remove()方法會拋出NoSuchElementException異常。因此,在使用remove()方法時,通常需要先檢查隊列是否為空,以避免異常的拋出。
一般來說,如果你不確定隊列是否為空,并希望在移除元素時得到一個特定的返回值來表示操作結(jié)果,可以使用poll()方法。如果你確定隊列不為空,并且希望在隊列為空時拋出異常,可以使用remove()方法。
總的來說,poll()方法在移除空隊列時返回null,而remove()方法則會拋出異常。根據(jù)具體的需求和處理方式,選擇適合的方法進行元素的移除操作。
31. 哪些集合類是線程安全的?
- Vector:Vector是線程安全的,它的方法都使用了synchronized關(guān)鍵字進行同步,適合在多線程環(huán)境中使用。
- Hashtable:Hashtable也是線程安全的,它的方法也使用了synchronized關(guān)鍵字進行同步,適合在多線程環(huán)境中使用。然而,Hashtable已經(jīng)在Java 1.2中被更現(xiàn)代化的HashMap取代,因為HashMap提供了更好的性能和擴展性。
- ConcurrentHashMap:ConcurrentHashMap是Java 5引入的高度并發(fā)的線程安全哈希表實現(xiàn)。它使用細粒度的鎖機制,使得多個線程可以同時讀取而不會阻塞,從而提高了并發(fā)性能。
- CopyOnWriteArrayList:CopyOnWriteArrayList是一個線程安全的列表實現(xiàn),它通過對每次寫操作都創(chuàng)建一個新的副本來實現(xiàn)線程安全。這意味著寫操作會比較耗時,但讀操作不會阻塞。
- ConcurrentLinkedQueue:ConcurrentLinkedQueue是一個線程安全的無界隊列實現(xiàn),它使用非阻塞算法,適用于多線程的生產(chǎn)者-消費者模式。
32. 迭代器 Iterator 是什么?
迭代器(Iterator)是Java集合框架中常用的一種設(shè)計模式,它提供了一種便捷的方式來遍歷并訪問集合中的元素,而不需要暴露集合內(nèi)部的結(jié)構(gòu)細節(jié)。
迭代器模式通過抽象出共同的遍歷行為,使得不同類型的集合類都可以用相同的方式進行遍歷操作。迭代器提供了一種統(tǒng)一的訪問集合元素的方式,無論集合是數(shù)組、鏈表還是其他數(shù)據(jù)結(jié)構(gòu),都可以通過迭代器按照一定的順序逐個訪問元素。
在Java中,迭代器是通過Iterator接口實現(xiàn)的。該接口定義了一些基本的方法,包括hasNext()用于判斷是否還有下一個元素,next()用于獲取下一個元素,remove()用于從集合中刪除當(dāng)前迭代過程中訪問的元素(可選操作)。
- boolean hasNext(): 檢查迭代器是否還有下一個元素。
- E next(): 返回迭代器的下一個元素,并將指針移動到下一個位置。
- void remove(): 從集合中移除迭代器最后訪問的元素(可選操作)。
迭代器模式的優(yōu)點在于它提供了一種與集合的具體實現(xiàn)解耦的方式,使得集合類可以獨立變化而不影響使用迭代器的代碼。同時,迭代器還提供了一種安全的方式來遍歷集合,避免了在遍歷過程中修改集合結(jié)構(gòu)導(dǎo)致的并發(fā)修改異常。
迭代器是集合的一個抽象表示,它提供了一個統(tǒng)一的界面來訪問集合中的元素,而不暴露底層的數(shù)據(jù)結(jié)構(gòu)和實現(xiàn)細節(jié)。
當(dāng)我們使用迭代器進行遍歷時,迭代器會維護一個內(nèi)部狀態(tài)來跟蹤當(dāng)前遍歷的位置。在遍歷過程中,如果集合發(fā)生了變化(比如添加、刪除元素),迭代器會注意到這個變化,并且在下一次檢索元素時,確保返回的元素是基于最新的集合狀態(tài)。
這樣一來,即使在遍歷過程中修改了集合的結(jié)構(gòu),例如刪除或添加元素,也不會出現(xiàn)并發(fā)修改異常(ConcurrentModificationException)或非法狀態(tài)異常(IllegalStateException)。迭代器會提供一個一致的、安全的遍歷方式,確保我們能夠正確地訪問集合中的元素。
需要注意的是,迭代器只能單向遍歷集合,即從前往后逐個訪問元素。如果需要逆向遍歷,可以考慮使用ListIterator接口,它是Iterator的子接口,提供了向前和向后遍歷的功能
a. 迭代器可以遍歷Map集合嗎
是的,迭代器可以用于遍歷Map集合。Map是一種鍵值對的集合,可以使用Entry對象來表示每個鍵值對。Map接口中的內(nèi)部Entry接口提供了getKey()和getValue()方法,可以用于獲取鍵和值。
在Java中,可以通過Map的entrySet()方法獲取包含所有鍵值對的Set集合,然后使用迭代器來遍歷這個集合。以下是一個示例:吧
Map<String, Integer> map = new HashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entrySet.iterator();while (iterator.hasNext()) {Map.Entry<String, Integer> entry = iterator.next();String key = entry.getKey();Integer value = entry.getValue();System.out.println("Key: " + key + ", Value: " + value);
}
33. Iterator 怎么使用?有什么特點?
- 獲取集合的迭代器對象:通過調(diào)用集合類的iterator()方法來獲取該集合的迭代器對象。例如:
List<String> myList = new ArrayList<>();
// 添加元素到集合
Iterator<String> iterator = myList.iterator();
- 遍歷集合元素:通過迭代器對象調(diào)用hasNext()方法來檢查是否還有下一個元素,然后通過next()方法獲取該元素。可以使用循環(huán)結(jié)構(gòu)(如while或for)來遍歷集合中的所有元素。例如:
while (iterator.hasNext()) {String element = iterator.next();// 對元素進行操作System.out.println(element);
}
- 可選操作:如果需要,在遍歷過程中可以調(diào)用迭代器的remove()方法刪除當(dāng)前迭代過程中訪問的元素。例如:
iterator.remove();
迭代器的特點如下:
- 迭代器提供了一種統(tǒng)一的訪問集合元素的方式,無論集合的具體實現(xiàn)是數(shù)組、鏈表或其他數(shù)據(jù)結(jié)構(gòu),我們都可以使用相同的代碼來進行遍歷操作。
- 迭代器實現(xiàn)了在遍歷過程中逐個獲取元素的功能,允許我們按照一定的順序遍歷一個集合。
- 迭代器是單向的,只能從前往后遍歷集合元素。如果需要逆向遍歷元素,可以考慮使用ListIterator接口。
- 迭代器提供了一種安全的遍歷方式,避免了在遍歷過程中修改集合結(jié)構(gòu)導(dǎo)致的并發(fā)修改異常。
34. Iterator 和 ListIterator 有什么區(qū)別?
Iterator和ListIterator都是Java集合框架中的迭代器接口,用于遍歷集合元素。它們之間的區(qū)別如下:
- 遍歷方向:
-
- Iterator只能單向遍歷集合元素,從前往后。
- ListIterator可以雙向遍歷集合元素,既可以從前往后,也可以從后往前。
- 定位能力:
-
- Iterator只能通過hasNext()和next()方法獲取下一個元素,并不能獲得當(dāng)前元素的索引位置。
- ListIterator在Iterator的基礎(chǔ)上增加了previous()和hasPrevious()方法,使其能夠向前遍歷并獲取前一個元素,同時還提供了nextIndex()和previousIndex()方法來獲取當(dāng)前元素的索引位置。
- 修改集合:
-
- Iterator提供了remove()方法,允許在遍歷過程中刪除當(dāng)前迭代的元素。
- ListIterator在Iterator的基礎(chǔ)上增加了add()和set()方法,可以在遍歷過程中添加新元素或修改當(dāng)前迭代的元素。
- 支持的集合類型:
-
- Iterator可用于遍歷大部分Java集合框架中的集合,如List、Set、Queue等。
- ListIterator主要用于遍歷List接口的實現(xiàn)類,如ArrayList、LinkedList等,因為List才具備向前遍歷和修改操作的能力。
35. 怎么確保一個集合不能被修改?
要確保一個集合不能被修改,可以采取以下幾種方法:
- 使用不可變集合:在Java中,有一些集合類是不可變的,即它們在創(chuàng)建后不能被修改。例如,Collections類提供了unmodifiableXXX()方法來創(chuàng)建不可變的集合,如unmodifiableList()、unmodifiableSet()和unmodifiableMap()等。通過使用這些方法,我們可以將可變的集合轉(zhuǎn)換為不可變的集合,從而防止對集合進行修改。
List<String> mutableList = new ArrayList<>();
mutableList.add("元素1");
mutableList.add("元素2");List<String> immutableList = Collections.unmodifiableList(mutableList);
immutableList.add("元素3"); // 會拋出UnsupportedOperationException異常
需要注意的是,雖然不可變集合本身不可修改,但如果集合中包含的元素是可變對象,那么這些元素的狀態(tài)可能是可以被修改的。
- 使用只讀接口引用:將集合的可變引用限制為只讀接口,而不是具體的可修改接口。例如,將可變的List引用聲明為List接口,而不是ArrayList類。
List<String> mutableList = new ArrayList<>(); // 可變的引用
List<String> readOnlyList = mutableList; // 只讀的引用
readOnlyList.add("元素1"); // 會拋出UnsupportedOperationException異常
這樣做可以防止通過只讀接口進行修改操作,但如果有其他對可變引用的引用,還是可以對集合進行修改。
- 自定義不可修改的集合類:自定義一個集合類,重寫修改集合的方法,并拋出UnsupportedOperationException異常來阻止修改操作。
public class ImmutableCollection<E> implements Collection<E> {// 省略實現(xiàn),重寫修改集合的方法,拋出UnsupportedOperationException異常
}
這種方法可以創(chuàng)建一個完全不可修改的集合類,無論是通過接口方法還是直接修改底層數(shù)據(jù)結(jié)構(gòu),都無法修改集合。
無論采用哪種方法,都需要注意的是,如果集合中的元素是可變對象,那么這些對象的狀態(tài)可能仍然可以被修改。要確保集合中的元素也是不可變的,需要采取相應(yīng)的措施來保證元素的不可變性。
另外,需要注意的是,以上方法只能防止直接修改集合本身的操作,對于集合中對象的屬性修改是無法控制的。如果需要完全保證集合及其元素的不可變性,可以考慮使用不可變對象或進行深度拷貝來防止任何修改。
?