1.什么是集合
集合框架:用于存儲數(shù)據(jù)的容器。
集合框架是為表示和操作集合而規(guī)定的一種統(tǒng)一的標(biāo)準(zhǔn)的體系結(jié)構(gòu)。 任何集合框架都包含三大塊內(nèi)容:對外的接口、接口的實現(xiàn)和對集合運算的算 法。
接口:表示集合的抽象數(shù)據(jù)類型。接口允許我們操作集合時不必關(guān)注具體實現(xiàn), 從而達(dá)到“多態(tài)”。在面向?qū)ο缶幊陶Z言中,接口通常用來形成規(guī)范。
實現(xiàn):集合接口的具體實現(xiàn),是重用性很高的數(shù)據(jù)結(jié)構(gòu)。
算法:在一個實現(xiàn)了某個集合框架中的接口的對象身上完成某種有用的計算的方 法,例如查找、排序等。這些算法通常是多態(tài)的,因為相同的方法可以在同一個 接口被多個類實現(xiàn)時有不同的表現(xiàn)。事實上,算法是可復(fù)用的函數(shù)。 它減少了程序設(shè)計的辛勞。
集合框架通過提供有用的數(shù)據(jù)結(jié)構(gòu)和算法使你能集中注意力于你的程序的重要部 分上,而不是為了讓程序能正常運轉(zhuǎn)而將注意力于底層設(shè)計上。
通過這些在無關(guān)API之間的簡易的互用性,使你免除了為改編對象或轉(zhuǎn)換代碼以 便聯(lián)合這些API而去寫大量的代碼。 它提高了程序速度和質(zhì)量。
2.集合的特點
集合的特點主要有如下兩點:
- 對象封裝數(shù)據(jù),對象多了也需要存儲。集合用于存儲對象。
- 對象的個數(shù)確定可以使用數(shù)組,對象的個數(shù)不確定的可以用集合。因 為集合是可變長度的。
3.集合和數(shù)組的區(qū)別
- 數(shù)組是固定長度的;集合可變長度的。
- 數(shù)組可以存儲基本數(shù)據(jù)類型,也可以存儲引用數(shù)據(jù)類型;集合只能存 儲引用數(shù)據(jù)類型。
- 數(shù)組存儲的元素必須是同一個數(shù)據(jù)類型;集合存儲的對象可以是不同 數(shù)據(jù)類型。
數(shù)據(jù)結(jié)構(gòu):就是容器中存儲數(shù)據(jù)的方式。
對于集合容器,有很多種。因為每一個容器的自身特點不同,其實原理在于每個 容器的內(nèi)部數(shù)據(jù)結(jié)構(gòu)不同。
集合容器在不斷向上抽取過程中,出現(xiàn)了集合體系。在使用一個體系的原則:參 閱頂層內(nèi)容。建立底層對象。
4.使用集合框架的好處
- 容量自增長;
- 提供了高性能的數(shù)據(jù)結(jié)構(gòu)和算法,使編碼更輕松,提高了程序速度和質(zhì) 量;
- 允許不同 API 之間的互操作,API之間可以來回傳遞集合;
- 可以方便地擴(kuò)展或改寫集合,提高代碼復(fù)用性和可操作性。
- 通過使用JDK自帶的集合類,可以降低代碼維護(hù)和學(xué)習(xí)新API成本。
5.常用的集合類有哪些?
Map接口和Collection接口是所有集合框架的父接口:
- Collection接口的子接口包括:Set接口和List接口
- Map接口的實現(xiàn)類主要有:HashMap、TreeMap、Hashtable、 ConcurrentHashMap以及Properties等
- Set接口的實現(xiàn)類主要有:HashSet、TreeSet、LinkedHashSet等
- List接口的實現(xiàn)類主要有:ArrayList、LinkedList、Stack以及Vector等
6.List,Set,Map三者的區(qū)別?List、Set、Map 是否繼 承自 Collection 接口?List、Map、Set 三個接口存取 元素時,各有什么特點?


Java 容器分為 Collection 和 Map 兩大類,Collection集合的子接口有Set、 List、Queue三種子接口。我們比較常用的是Set、List,Map接口不是 collection的子接口。
Collection集合主要有List和Set兩大接口
- List:一個有序(元素存入集合的順序和取出的順序一致)容器,元素可以重 復(fù),可以插入多個null元素,元素都有索引。常用的實現(xiàn)類有 ArrayList、LinkedList 和 Vector。
- Set:一個無序(存入和取出順序有可能不一致)容器,不可以存儲重復(fù)元素, 只允許存入一個null元素,必須保證元素唯一性。Set 接口常用實現(xiàn)類是 HashSet、 LinkedHashSet 以及 TreeSet。
Map是一個鍵值對集合,存儲鍵、值和之間的映射。 Key無序,唯一;value 不 要求有序,允許重復(fù)。Map沒有繼承于Collection接口,從Map集合中檢索元 素時,只要給出鍵對象,就會返回對應(yīng)的值對象。
Map 的常用實現(xiàn)類:HashMap、TreeMap、HashTable、LinkedHashMap、 ConcurrentHashMap
7.集合框架底層數(shù)據(jù)結(jié)構(gòu)
Collection
**List **
- Arraylist: Object數(shù)組
- Vector: Object數(shù)組
- LinkedList: 雙向循環(huán)鏈表
**Set **
- HashSet(無序,唯一):基于 HashMap 實現(xiàn)的,底層采用 HashMap 來保存元素
- LinkedHashSet: LinkedHashSet 繼承與 HashSet,并且其內(nèi)部是通過 LinkedHashMap 來實現(xiàn)的。有點類似于我們之前說的LinkedHashMap 其內(nèi)部是基 于 Hashmap 實現(xiàn)一樣,不過還是有一點點區(qū)別的。
- LinkedHashSet是Set集合的一個實現(xiàn),具有set集合不重復(fù)的特點,同時具有可預(yù)測的迭代順序,也就是我們插入的順序。并且linkedHashSet是一個非線程安全的集合。如果有多個線程同時訪問當(dāng)前l(fā)inkedhashset集合容器,并且有一個線程對當(dāng)前容器中的元素做了修改,那么必須要在外部實現(xiàn)同步保證數(shù)據(jù)的冥等性
- TreeSet(有序,唯一): 紅黑樹(自平衡的排序二叉樹。
- Map
- HashMap: JDK1.8之前HashMap由數(shù)組+鏈表組成的,數(shù)組是HashMap的主 體,鏈表則是主要為了解決哈希沖突而存在的(“拉鏈法”解決沖突).JDK1.8以后
在解決哈希沖突時有了較大的變化,當(dāng)鏈表長度大于閾值(默認(rèn)為8)時,將鏈表轉(zhuǎn) 化為紅黑樹,以減少搜索時間 - LinkedHashMap:LinkedHashMap 繼承自 HashMap,所以它的底層仍然是 基于拉鏈?zhǔn)缴⒘薪Y(jié)構(gòu)即由數(shù)組和鏈表或紅黑樹組成。另外,LinkedHashMap 在上面 結(jié)構(gòu)的基礎(chǔ)上,增加了一條雙向鏈表,使得上面的結(jié)構(gòu)可以保持鍵值對的插入順序。 同時通過對鏈表進(jìn)行相應(yīng)的操作,實現(xiàn)了訪問順序相關(guān)邏輯。
- HashTable: 數(shù)組+鏈表組成的,數(shù)組是 HashMap 的主體,鏈表則是主要為 了解決哈希沖突而存在的
- TreeMap: 紅黑樹(自平衡的排序二叉樹)
8.哪些集合類是線程安全的?
- vector:就比arraylist多了個同步化機制(線程安全),因為效率較低,現(xiàn)在已 經(jīng)不太建議使用。在web應(yīng)用中,特別是前臺頁面,往往效率(頁面響應(yīng)速度)是優(yōu) 先考慮的。
- statck:堆棧類,先進(jìn)后出。
- hashtable:就比hashmap多了個線程安全。
- enumeration:枚舉,相當(dāng)于迭代器。
9.Java集合的快速失敗機制 “fail-fast”?
是java集合的一種錯誤檢測機制,當(dāng)多個線程對集合進(jìn)行結(jié)構(gòu)上的改變的操作 時,有可能會產(chǎn)生 fail-fast 機制。
例如:假設(shè)存在兩個線程(線程1、線程2),線程1通過Iterator在遍歷集合A中 的元素,在某個時候線程2修改了集合A的結(jié)構(gòu)(是結(jié)構(gòu)上面的修改,而不是簡 單的修改集合元素的內(nèi)容),那么這個時候程序就會拋出ConcurrentModificationException 異常,從而產(chǎn)生fail-fast機制。
原因:迭代器在遍歷時直接訪問集合中的內(nèi)容,并且在遍歷過程中使用一個 modCount 變量。集合在被遍歷期間如果內(nèi)容發(fā)生變化,就會改變modCount 的值。每當(dāng)?shù)魇褂胔ashNext()/next()遍歷下一個元素之前,都會檢測 modCount變量是否為expectedmodCount值,是的話就返回遍歷;否則拋出 異常,終止遍歷。
解決辦法:
- 在遍歷過程中,所有涉及到改變modCount值得地方全部加上 synchronized。
- 使用CopyOnWriteArrayList來替換ArrayList
10.怎么確保一個集合不能被修改?
可以使用 Collections. unmodifiableCollection(Collection c) 方法來創(chuàng)建一個 只讀集合,這樣改變集合的任何操作都會拋出 Java. lang. UnsupportedOperationException 異常。 示例代碼如下:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 運行時此行報錯
System. out. println(list. size());
11.迭代器 Iterator 是什么?
Iterator 接口提供遍歷任何 Collection 的接口。我們可以從一個 Collection 中使用迭代器方法來獲取迭代器實例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允許調(diào)用者在迭代過程中移除元素。
12.Iterator 怎么使用?有什么特點?
Iterator 使用代碼如下:
1 List<String> list = new ArrayList
2 Iterator<String> it = list. iterator
3 while(it. hasNext()){
4 String obj = it. next();
5 System. out. println(obj);
6 }
Iterator 的特點是只能單向遍歷,但是更加安全,因為它可以確保,在當(dāng)前遍歷的集合元素被更改的時候,就會拋出 ConcurrentModificationException 異常。
13.如何邊遍歷邊移除 Collection 中的元素?
邊遍歷邊修改 Collection 的唯一正確方式是使用 Iterator.remove() 方法,如下:
1 Iterator<Integer> it = list.iterator();
2 while(it.hasNext()){
3 *// do something*
4 it.remove();5
5 }
一種常見的錯誤代碼如下:
1 for(Integer i : list){
2 list.remove(i)
3 }
運行以上錯誤代碼會報 ConcurrentModificationException 異常。這是因為當(dāng)使用 foreach(for(Integer i : list)) 語句時,會自動生成一個iterator 來遍歷該 list,但同時該 list 正在被 Iterator.remove() 修改。Java 一般不允許一個線程在遍歷 Collection 時另一個線程修改它。
14.Iterator 和 ListIterator 有什么區(qū)別?
- Iterator 可以遍歷 Set 和 List 集合,而 ListIterator 只能遍歷 List。
- Iterator 只能單向遍歷,而 ListIterator 可以雙向遍歷(向前/后遍歷)。
- ListIterator 實現(xiàn) Iterator 接口,然后添加了一些額外的功能,比如添加一個元素、替換一個元素、獲取前面或后面元素的索引位置。
15.遍歷一個 List 有哪些不同的方式?每種方法的實現(xiàn)原理是什么?Java 中 List 遍歷的最佳實踐是什么?
遍歷方式有以下幾種:
for 循環(huán)遍歷,基于計數(shù)器。在集合外部維護(hù)一個計數(shù)器,然后依次讀 取每一個位置的元素,當(dāng)讀取到后一個元素后停止。
迭代器遍歷,Iterator。Iterator 是面向?qū)ο蟮囊粋€設(shè)計模式,目的是屏 蔽不同數(shù)據(jù)集合的特點,統(tǒng)一遍歷集合的接口。Java 在 Collections 中支 持了 Iterator 模式。
foreach 循環(huán)遍歷。foreach 內(nèi)部也是采用了 Iterator 的方式實現(xiàn),使 用時不需要顯式聲明 Iterator 或計數(shù)器。優(yōu)點是代碼簡潔,不易出錯;缺 點是只能做簡單的遍歷,不能在遍歷過程中操作數(shù)據(jù)集合,例如刪除、替 換。
最佳實踐:Java Collections 框架中提供了一個 RandomAccess 接口,用來標(biāo) 記 List 實現(xiàn)是否支持 Random Access。
- 如果一個數(shù)據(jù)集合實現(xiàn)了該接口,就意味著它支持 Random Access,按位置讀 取元素的平均時間復(fù)雜度為 O(1),如ArrayList。
- 如果沒有實現(xiàn)該接口,表示不支持 Random Access,如LinkedList。 推薦的做法就是,支持 Random Access 的列表可用 for 循環(huán)遍歷,否則建議 用 Iterator 或 foreach 遍歷。
16.說一下 ArrayList 的優(yōu)缺點
ArrayList的優(yōu)點如下:
- ArrayList 底層以數(shù)組實現(xiàn),是一種隨機訪問模式。ArrayList 實現(xiàn)了 RandomAccess 接口,因此查找的時候非???。
- ArrayList 在順序添加一個元素的時候非常方便。
ArrayList 的缺點如下:
- 刪除元素的時候,需要做一次元素復(fù)制操作。如果要復(fù)制的元素很多,那么就會比較耗費性能。
- 插入元素的時候,也需要做一次元素復(fù)制操作,缺點同上。
ArrayList 比較適合順序添加、隨機訪問的場景。
17.如何實現(xiàn)數(shù)組和 List 之間的轉(zhuǎn)換?
數(shù)組轉(zhuǎn) List:使用 Arrays. asList(array) 進(jìn)行轉(zhuǎn)換。
List 轉(zhuǎn)數(shù)組:使用 List 自帶的 toArray() 方法。代碼示例:
1 // list to array
2 List<String> list = new ArrayList<String>();
3 list.add("123");
4 list.add("456");
5 list.toArray();
6
7 // array to list
8 String[] array = new String[]{"123","456"};
9 Arrays.asList(array);
18.ArrayList 和 LinkedList 的區(qū)別是什么?
- 數(shù)據(jù)結(jié)構(gòu)實現(xiàn):ArrayList 是動態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu)實現(xiàn),而 LinkedList 是雙向鏈表的數(shù)據(jù)結(jié)構(gòu)實現(xiàn)。
- 隨機訪問效率:ArrayList 比 LinkedList 在隨機訪問的時候效率要高,因為 LinkedList 是線性的數(shù)據(jù)存儲方式,所以需要移動指針從前往后依次查找。
- 增加和刪除效率:在非首尾的增加和刪除操作,LinkedList 要比 ArrayList 效率要高,因為 ArrayList 增刪操作要影響數(shù)組內(nèi)的其他數(shù)據(jù)的下標(biāo)。
- 內(nèi)存空間占用:LinkedList 比 ArrayList 更占內(nèi)存,因為 LinkedList 的節(jié)點除了存儲數(shù)據(jù),還存儲了兩個引用,一個指向前一個元素,一個指向后一個元素。
- 線程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保證線程安全;
綜合來說,在需要頻繁讀取集合中的元素時,更推薦使用 ArrayList,而在插入和刪除操作較多時,更推薦使用 LinkedList。
補充:數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)之雙向鏈表
雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數(shù)據(jù)結(jié)點中都有兩個指針,分別指向直接后繼和直接前驅(qū)。所以,從雙向鏈表中的任意一個結(jié)點開始,都可以很方便地訪問它的前驅(qū)結(jié)點和后繼結(jié)點。
19.ArrayList 和 Vector 的區(qū)別是什么?
這兩個類都實現(xiàn)了 List 接口(List 接口繼承了 Collection 接口),他們都是有序集合
線程安全:Vector 使用了 Synchronized 來實現(xiàn)線程同步,是線程安全的,而ArrayList 是非線程安全的。
性能:ArrayList 在性能方面要優(yōu)于 Vector。
擴(kuò)容:ArrayList 和 Vector 都會根據(jù)實際的需要動態(tài)的調(diào)整容量,只不過在
Vector 擴(kuò)容每次會增加 1 倍,而 ArrayList 只會增加 50%。
Vector類的所有方法都是同步的??梢杂蓛蓚€線程安全地訪問一個Vector對
象、但是一個線程訪問Vector的話代碼要在同步操作上耗費大量的時間。
Arraylist不是同步的,所以在不需要保證線程安全時時建議使用Arraylist。
20.插入數(shù)據(jù)時,ArrayList、LinkedList、Vector誰速度較快?闡述 ArrayList、Vector、LinkedList 的存儲性能和特性?
ArrayList、LinkedList、Vector 底層的實現(xiàn)都是使用數(shù)組方式存儲數(shù)據(jù)。數(shù)組
元素數(shù)大于實際存儲的數(shù)據(jù)以便增加和插入元素,它們都允許直接按序號索引元素,但是插入元素要涉及數(shù)組元素移動等內(nèi)存操作,所以索引數(shù)據(jù)快而插入數(shù)據(jù)慢。
Vector 中的方法由于加了 synchronized 修飾,因此 Vector是線程安全容器,但性能上較ArrayList差。
LinkedList 使用雙向鏈表實現(xiàn)存儲,按序號索引數(shù)據(jù)需要進(jìn)行前向或后向遍歷,但插入數(shù)據(jù)時只需要記錄當(dāng)前項的前后項即可,所以 LinkedList插入速度較快。
21.多線程場景下如何使用 ArrayList?
ArrayList 不是線程安全的,如果遇到多線程場景,可以通過 Collections 的
synchronizedList 方法將其轉(zhuǎn)換成線程安全的容器后再使用。例如像下面這樣:
22.多線程場景下如何使用 ArrayList?
ArrayList 不是線程安全的,如果遇到多線程場景,可以通過 Collections 的
synchronizedList 方法將其轉(zhuǎn)換成線程安全的容器后再使用。例如像下面這樣:
2 synchronizedList.add("aaa");
3 synchronizedList.add("bbb");
4
5 for (int i = 0; i < synchronizedList.size(); i++) {
6 System.out.println(synchronizedList.get(i));
7 }
23.為什么 ArrayList 的 elementData 加上 transient 修飾? ArrayList 中的數(shù)組定義如下:
private transient Object[] elementData;
再看一下 ArrayList 的定義:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
可以看到 ArrayList 實現(xiàn)了 Serializable 接口,這意味著 ArrayList 支持序列化。transient 的作用是說不希望 elementData 數(shù)組被序列化,重寫了writeObject 實現(xiàn):
1 private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOE xception{
2 *// Write out element count, and any hidden stuff*
3 int expectedModCount = modCount;
4 s.defaultWriteObject();
5 *// Write out array length*
6 s.writeInt(elementData.length);
7 *// Write out all elements in the proper order.*
8 for (int i=0; i<size; i++)
9 s.writeObject(elementData[i]);
10 if (modCount != expectedModCount) {
11 throw new ConcurrentModificationException();
12 }
每次序列化時,先調(diào)用 defaultWriteObject() 方法序列化 ArrayList 中的非transient 元素,然后遍歷 elementData,只序列化已存入的元素,這樣既加快了序列化的速度,又減小了序列化之后的文件大小。
24.List 和 Set 的區(qū)別
List , Set 都是繼承自Collection 接口
List 特點:一個有序(元素存入集合的順序和取出的順序一致)容器,元素可以重復(fù),可以插入多個null元素,元素都有索引。常用的實現(xiàn)類有 ArrayList、
LinkedList 和 Vector。
Set 特點:一個無序(存入和取出順序有可能不一致)容器,不可以存儲重復(fù)元素,只允許存入一個null元素,必須保證元素唯一性。Set 接口常用實現(xiàn)類是
HashSet、LinkedHashSet 以及 TreeSet。
另外 List 支持for循環(huán),也就是通過下標(biāo)來遍歷,也可以用迭代器,但是set只能用迭代,因為他無序,無法用下標(biāo)來取得想要的值。
Set和List對比
Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變。
List:和數(shù)組類似,List可以動態(tài)增長,查找元素效率高,插入刪除元素效率低,因為會引起其他元素位置改變
25.說一下 HashSet 的實現(xiàn)原理?
HashSet 是基于 HashMap 實現(xiàn)的,HashSet的值存放于HashMap的key上,
HashMap的value統(tǒng)一為PRESENT,因此 HashSet 的實現(xiàn)比較簡單,相關(guān) HashSet 的操作,基本上都是直接調(diào)用底層 HashMap 的相關(guān)方法來完成,
HashSet 不允許重復(fù)的值。
26.HashSet如何檢查重復(fù)?HashSet是如何保證數(shù)據(jù)不可重復(fù)的?
向HashSet 中add ()元素時,判斷元素是否存在的依據(jù),不僅要比較hash值,同時還要結(jié)合equles 方法比較。
HashSet 中的add ()方法會使用HashMap 的put()方法。
HashMap 的 key 是唯一的,由源碼可以看出 HashSet 添加進(jìn)去的值就是作為 HashMap 的key,并且在HashMap中如果K/V相同時,會用新的V覆蓋掉舊的V,然后返回舊的V。所以不會重復(fù)( HashMap 比較key是否相等是先比較 hashcode 再比較equals )。
以下是HashSet 部分源碼:
1 private static final Object PRESENT = new Object();
2 private transient HashMap<E,Object> map;
3
4 public HashSet() {
<>
5 map = new HashMap ();
6 }
7
8 public boolean add(E e) {
9 // 調(diào)用HashMap的put方法,PRESENT是一個至始至終都相同的虛值
10 return map.put(e, PRESENT)==null;
11 }
hashCode()與equals()的相關(guān)規(guī)定:
如果兩個對象相等,則hashcode一定也是相同的
兩個對象相等,對兩個equals方法返回true
兩個對象有相同的hashcode值,它們也不一定是相等的
綜上,equals方法被覆蓋過,則hashCode方法也必須被覆蓋
hashCode()的默認(rèn)行為是對堆上的對象產(chǎn)生獨特值。如果沒有重寫hashCode(),則該class的兩個對象無論如何都不會相等(即使這兩個對象指向相同的數(shù)據(jù))。
==與equals的區(qū)別
==是判斷兩個變量或?qū)嵗遣皇侵赶蛲粋€內(nèi)存空間 equals是判斷兩個變量或?qū)嵗赶虻膬?nèi)存空間的值是不是相同
==是指對內(nèi)存地址進(jìn)行比較 equals()是對字符串的內(nèi)容進(jìn)行比較3.== 指引用是否相同 equals()指的是值是否相同
27.HashSet與HashMap的區(qū)別
| HashMap | HashSet |
|---|---|
| 實現(xiàn)了Map接口 | 實現(xiàn)了Set接口 |
| 存儲鍵值對 | 僅存儲對象 |
| 調(diào)用 put()向 map中添加元素 | 調(diào)用 add() 方法向Set 中添加元素 |
| HashMap 使用鍵 (Key)計算 Hashcode | HashSet 使用成員對象來計 算 hashcode 值,對于兩個對象 來說 hashcode 可能相 同,所以 equals()方法用來判斷對象的相等性,如果兩個對象不同的話,那 么返回 false |
| HashMap 相對于 HashSet 較快,因為它是使用唯一的鍵獲取對象 | HashSet 較 HashMap 來說比較慢 |
28.BlockingQueue是什么?
Java.util.concurrent.BlockingQueue是一個隊列,在進(jìn)行檢索或移除一個元素的時候,它會等待隊列變?yōu)榉强?;?dāng)在添加一個元素時,它會等待隊列中的可用空間。BlockingQueue接口是Java集合框架的一部分,主要用于實現(xiàn)生產(chǎn)者-消費者模式。我們不需要擔(dān)心等待生產(chǎn)者有可用的空間,或消費者有可用的對象,因為它都在BlockingQueue的實現(xiàn)類中被處理了。Java提供了集中 BlockingQueue的實現(xiàn),比如ArrayBlockingQueue、
LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。在 Queue 中 poll()和 remove()有什么區(qū)別?
- 相同點:都是返回第一個元素,并在隊列中刪除返回的對象。
- 不同點:如果沒有元素 poll()會返回 null,而 remove()會直接拋出 NoSuchElementException 異常。
代碼示例:
1 Queue<String> queue = new LinkedList<String>();
2 queue. offer("string"); // add
3 System. out. println(queue. poll());
4 System. out. println(queue. remove());
5 System. out. println(queue. size());
29.說一下 HashMap 的實現(xiàn)原理?
HashMap概述: HashMap是基于哈希表的Map接口的非同步實現(xiàn)。此實現(xiàn)提供所有可選的映射操作,并允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恒久不變。
HashMap的數(shù)據(jù)結(jié)構(gòu): 在Java編程語言中, 基本的結(jié)構(gòu)就是兩種,一個是數(shù)組,另外一個是模擬指針(引用),所有的數(shù)據(jù)結(jié)構(gòu)都可以用這兩個基本結(jié)構(gòu)來構(gòu)造的,HashMap也不例外。HashMap實際上是一個“鏈表散列”的數(shù)據(jù)結(jié)構(gòu),即數(shù)組和鏈表的結(jié)合體。
HashMap 基于 Hash 算法實現(xiàn)的
當(dāng)我們往Hashmap中put元素時,利用key的hashCode重新hash計算出當(dāng)前對象的元素在數(shù)組中的下標(biāo)
存儲時,如果出現(xiàn)hash值相同的key,此時有兩種情況。(1)如果key相
同,則覆蓋原始值;(2)如果key不同(出現(xiàn)沖突),則將當(dāng)前的key-value 放入鏈表中
獲取時,直接找到hash值對應(yīng)的下標(biāo),在進(jìn)一步判斷key是否相同,從而找到對應(yīng)值。
理解了以上過程就不難明白HashMap是如何解決hash沖突的問題,核心就是使用了數(shù)組的存儲方式,然后將沖突的key的對象放入鏈表中,一旦發(fā)現(xiàn)沖突就在鏈表中做進(jìn)一步的對比。
需要注意Jdk 1.8中對HashMap的實現(xiàn)做了優(yōu)化,當(dāng)鏈表中的節(jié)點數(shù)據(jù)超過八個之后,該鏈表會轉(zhuǎn)為紅黑樹來提高查詢效率,從原來的O(n)到O(logn)
30.HashMap在JDK1.7和JDK1.8中有哪些不同? HashMap的底層實現(xiàn)
在Java中,保存數(shù)據(jù)有兩種比較簡單的數(shù)據(jù)結(jié)構(gòu):數(shù)組和鏈表。數(shù)組的特點是:尋址容易,插入和刪除困難;鏈表的特點是:尋址困難,但插入和刪除容易;所以我們將數(shù)組和鏈表結(jié)合在一起,發(fā)揮兩者各自的優(yōu)勢,使用一種叫做拉鏈法的方式可以解決哈希沖突。
JDK1.8之前
JDK1.8之前采用的是拉鏈法。拉鏈法:將鏈表和數(shù)組相結(jié)合。也就是說創(chuàng)建一個鏈表數(shù)組,數(shù)組中每一格就是一個鏈表。若遇到哈希沖突,則將沖突的值加到鏈表中即可。
JDK1.8之后
相比于之前的版本,jdk1.8在解決哈希沖突時有了較大的變化,當(dāng)鏈表長度大于閾值(默認(rèn)為8)時,將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時間。
JDK1.7 VS JDK1.8 比較
JDK1.8主要解決或優(yōu)化了一下問題:
resize 擴(kuò)容優(yōu)化
引入了紅黑樹,目的是避免單條鏈表過長而影響查詢效率,紅黑樹算法請參考
-
解決了多線程死循環(huán)問題,但仍是非線程安全的,多線程時可能會造成數(shù)據(jù)丟失問題。
image.png
31.HashMap的put方法的具體流程?
當(dāng)我們put的時候,首先計算 key的hash值,這里調(diào)用了 hash方法,hash方法實際是讓key.hashCode()與key.hashCode()>>>16進(jìn)行異或操作,高16bit補0,一個數(shù)和0異或不變,所以 hash 函數(shù)大概的作用就是:高16bit不變,低16bit和高
16bit做了一個異或,目的是減少碰撞。按照函數(shù)注釋,因為bucket數(shù)組大小是2的冪,計算下標(biāo)index = (table.length - 1) & hash,如果不做 hash 處理,相當(dāng)于散列生效的只有幾個低 bit 位,為了減少散列的碰撞,設(shè)計者綜合考慮了速度、作用、質(zhì)量之后,使用高16bit和低16bit異或來簡單處理減少碰撞,而且
JDK8中用了復(fù)雜度 O(logn)的樹結(jié)構(gòu)來提升碰撞下的性能。
putVal方法執(zhí)行流程圖
①.判斷鍵值對數(shù)組table[i]是否為空或為null,否則執(zhí)行resize()進(jìn)行擴(kuò)容;
②.根據(jù)鍵值key計算hash值得到插入的數(shù)組索引i,如果table[i]==null,直接新建節(jié)點添加,轉(zhuǎn)向⑥,如果table[i]不為空,轉(zhuǎn)向③;
③.判斷table[i]的首個元素是否和key一樣,如果相同直接覆蓋value,否則轉(zhuǎn)向
④,這里的相同指的是hashCode以及equals;
④.判斷table[i] 是否為treeNode,即table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對,否則轉(zhuǎn)向⑤;
⑤.遍歷table[i],判斷鏈表長度是否大于8,大于8的話把鏈表轉(zhuǎn)換為紅黑樹,在紅黑樹中執(zhí)行插入操作,否則進(jìn)行鏈表的插入操作;遍歷過程中若發(fā)現(xiàn)key已經(jīng)存在直接覆蓋value即可;
⑥.插入成功后,判斷實際存在的鍵值對數(shù)量size是否超多了 大容量threshold,如果超過,進(jìn)行擴(kuò)容。
32.HashMap的擴(kuò)容操作是怎么實現(xiàn)的?
①.在jdk1.8中,resize方法是在hashmap中的鍵值對大于閥值時或者初始化時,就調(diào)用resize方法進(jìn)行擴(kuò)容;
②.每次擴(kuò)展的時候,都是擴(kuò)展2倍;
③.擴(kuò)展后Node對象的位置要么在原位置,要么移動到原偏移量兩倍的位置。在putVal()中,我們看到在這個函數(shù)里面使用到了2次resize()方法,resize()方法表示的在進(jìn)行第一次初始化時會對其進(jìn)行擴(kuò)容,或者當(dāng)該數(shù)組的實際大小大于其臨界值值(第一次為12),這個時候在擴(kuò)容的同時也會伴隨的桶上面的元素進(jìn)行重新分發(fā),這也是JDK1.8版本的一個優(yōu)化的地方,在1.7中,擴(kuò)容之后需要重新去計算其Hash值,根據(jù)Hash值對其進(jìn)行分發(fā),但在1.8版本中,則是根據(jù)在同一個桶的位置中進(jìn)行判斷(e.hash & oldCap)是否為0,重新進(jìn)行hash分配后,該元素的位置要么停留在原始位置,要么移動到原始位置+增加的數(shù)組大小這個位置上
33.HashMap是怎么解決哈希沖突的?
答:在解決這個問題之前,我們首先需要知道什么是哈希沖突,而在了解哈希沖突之前我們還要知道什么是哈希才行;什么是哈希?
Hash,一般翻譯為“散列”,也有直接音譯為“哈?!钡?,這就是把任意長度的輸入通過散列算法,變換成固定長度的輸出,該輸出就是散列值(哈希值);這種轉(zhuǎn)換是一種壓縮映射,也就是,散列值的空間通常遠(yuǎn)小于輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來唯一的確定輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數(shù)。
所有散列函數(shù)都有如下一個基本特性:根據(jù)同一散列函數(shù)計算出的散列值如果不同,那么輸入值肯定也不同。但是,根據(jù)同一散列函數(shù)計算出的散列值如果相同,輸入值不一定相同。
什么是哈希沖突?
當(dāng)兩個不同的輸入值,根據(jù)同一散列函數(shù)計算出相同的散列值的現(xiàn)象,我們就把它叫做碰撞(哈希碰撞)。
HashMap的數(shù)據(jù)結(jié)構(gòu)
在Java中,保存數(shù)據(jù)有兩種比較簡單的數(shù)據(jù)結(jié)構(gòu):數(shù)組和鏈表。數(shù)組的特點是:尋址容易,插入和刪除困難;鏈表的特點是:尋址困難,但插入和刪除容易;所以我們將數(shù)組和鏈表結(jié)合在一起,發(fā)揮兩者各自的優(yōu)勢,使用一種叫做鏈地址法的方式可以解決哈希沖突:
這樣我們就可以將擁有相同哈希值的對象(img)組織成一個鏈表放在hash值所對應(yīng)的 bucket下,但相比于hashCode返回的int類型,我們HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即2的四次方16)要遠(yuǎn)小于int類型的范圍,所以我們?nèi)绻皇菃渭兊挠胔ashCode取余來獲取對應(yīng)的bucket這將會大大增加哈希碰撞的概率,并且最壞情況下還會將HashMap變成一個單鏈表,所以我們還需要對hashCode作一定的優(yōu)化 hash()函數(shù)
上面提到的問題,主要是因為如果使用hashCode取余,那么相當(dāng)于參與運算的只有hashCode的低位,高位是沒有起到任何作用的,所以我們的思路就是讓 hashCode取值出的高位也參與運算,進(jìn)一步降低hash
碰撞的概率,使得數(shù)據(jù)分布更平均,我們把這樣的操作稱為擾動,在JDK 1.8中的hash()函數(shù)如下:
這比在JDK 1.7中,更為簡潔,相比在1.7中的4次位運算,5次異或運算(9次擾動),在1.8中,只進(jìn)行了1次位運算和1次異或運算(2次擾動);
34.能否使用任何類作為 Map 的 key?
可以使用任何類作為 Map 的 key,然而在使用之前,需要考慮以下幾點: 如果類重寫了 equals() 方法,也應(yīng)該重寫 hashCode() 方法。類的所有實例需要遵循與 equals() 和 hashCode() 相關(guān)的規(guī)則。
如果一個類沒有使用 equals(),不應(yīng)該在 hashCode() 中使用它。
用戶自定義 Key 類 佳實踐是使之為不可變的,這樣 hashCode() 值可以被緩存起來,擁有更好的性能。不可變的類也可以確保 hashCode() 和 equals() 在未來不會改變,這樣就會解決與可變相關(guān)的問題了。
35.為什么HashMap中String、Integer這樣的包裝類適合作為K?
答:String、Integer等包裝類的特性能夠保證Hash值的不可更改性和計算準(zhǔn)確性,能夠有效的減少Hash碰撞的幾率
- 都是final類型,即不可變性,保證key的不可更改性,不會存在獲取 hash值不同的情況
內(nèi)部已重寫了equals()、hashCode()等方法,遵守了HashMap內(nèi)部的規(guī)范(不清楚可以去上面看看putValue的過程),不容易出現(xiàn)Hash值計算錯誤的情況)
36.如果使用Object作為HashMap的Key,應(yīng)該怎么辦呢?
答:重寫hashCode()和equals()方法
重寫hashCode()是因為需要計算存儲數(shù)據(jù)的存儲位置,需要注意不要試圖從散列碼計算中排除掉一個對象的關(guān)鍵部分來提高性能,這樣雖然能更快但可能會導(dǎo)致更多的Hash碰撞;
重寫equals()方法,需要遵守自反性、對稱性、傳遞性、一致性以及對于任何非null的引用值x,x.equals(null)必須返回false的這幾個特性,目的是為了保證key在哈希表中的唯一性
37.HashMap為什么不直接使用hashCode()處理后的哈希 值直接作為table的下標(biāo)?
答:hashCode()方法返回的是int整數(shù)類型,其范圍為-(2 ^ 31)~(2 ^ 31 - 1),約有40億個映射空間,而HashMap的容量范圍是在16(初始化默認(rèn)值)~2 ^ 30,HashMap通常情況下是取不到 大值的,并且設(shè)備上也難以提供這么多的存儲空間,從而導(dǎo)致通過hashCode()計算出的哈希值可能不在數(shù)組大小范圍內(nèi),進(jìn)而無法匹配存儲位置;
那怎么解決呢?
HashMap自己實現(xiàn)了自己的hash()方法,通過兩次擾動使得它自己的哈希值高低位自行進(jìn)行異或運算,降低哈希碰撞概率也使得數(shù)據(jù)分布更平均;
在保證數(shù)組長度為2的冪次方的時候,使用hash()運算之后的值與運算
(&)(數(shù)組長度 - 1)來獲取數(shù)組下標(biāo)的方式進(jìn)行存儲,這樣一來是比取
余操作更加有效率,二來也是因為只有當(dāng)數(shù)組長度為2的冪次方時,h&
(length-1)才等價于h%length,三來解決了“哈希值與數(shù)組大小范圍不匹配"的問題
38.HashMap 的長度為什么是2的冪次方
為了能讓 HashMap 存取高效,盡量較少碰撞,也就是要盡量把數(shù)據(jù)分配均勻,每個鏈表/紅黑樹長度大致相同。這個實現(xiàn)就是把數(shù)據(jù)存到哪個鏈表/紅黑樹中的算法。
這個算法應(yīng)該如何設(shè)計呢?我們首先可能會想到采用%取余的操作來實現(xiàn)。但是,重點來了:“取余(%)操作中如果除數(shù)是2的冪次則等價于與其除數(shù)減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)?!?并且 采用二進(jìn)制位操作 &,相對于%能夠提高運算效率,這就解釋了 HashMap 的長度為什么是2的冪次方。
那為什么是兩次擾動呢?答:這樣就是加大哈希值低位的隨機性,使得分布更均勻,從而提高對應(yīng)數(shù)組存儲下標(biāo)位置的隨機性&均勻性, 終減少Hash沖突,兩次就夠了,已經(jīng)達(dá)到了高位低位同時參與運算的目的。
39.HashMap 與 HashTable 有什么區(qū)別?
- 線程安全: HashMap 是非線程安全的,HashTable 是線程安全的;
HashTable 內(nèi)部的方法基本都經(jīng)過 synchronized 修飾。(如果你要保證線程安全的話就使用 ConcurrentHashMap 吧?。?/p>
效率: 因為線程安全的問題,HashMap 要比 HashTable 效率高一點。另外,HashTable 基本被淘汰,不要在代碼中使用它;
對Null key 和Null value的支持: HashMap 中,null 可以作為鍵,這樣的鍵只有一個,可以有一個或多個鍵所對應(yīng)的值為 null。但是在
HashTable 中 put 進(jìn)的鍵值只要有一個 null,直接拋NullPointerException。
**初始容量大小和每次擴(kuò)充容量大小的不同 **: ①創(chuàng)建時如果不指定容量初始值,Hashtable 默認(rèn)的初始大小為11,之后每次擴(kuò)充,容量變?yōu)樵瓉淼?n+1。HashMap 默認(rèn)的初始化大小為16。之后每次擴(kuò)充,容量變?yōu)樵瓉淼?倍。②創(chuàng)建時如果給定了容量初始值,那么 Hashtable 會直接使用你給定的大小,而 HashMap 會將其擴(kuò)充為2的冪次方大小。也就是說 HashMap 總是使用2的冪作為哈希表的大小,后面會介紹到為什么是2 的冪次方。
底層數(shù)據(jù)結(jié)構(gòu): JDK1.8 以后的 HashMap 在解決哈希沖突時有了較大的變化,當(dāng)鏈表長度大于閾值(默認(rèn)為8)時,將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時間。Hashtable 沒有這樣的機制。
推薦使用:在 Hashtable 的類注釋可以看到,Hashtable 是保留類不建議使用,推薦在單線程環(huán)境下使用 HashMap 替代,如果需要多線程使用則用 ConcurrentHashMap 替代。
40.如何決定使用 HashMap 還是TreeMap?
對于在Map中插入、刪除和定位元素這類操作,HashMap是 好的選擇。然而,假如你需要對一個有序的key集合進(jìn)行遍歷,TreeMap是更好的選擇。基于你的collection的大小,也許向HashMap中添加元素會更快,將map換為TreeMap進(jìn)行有序key的遍歷。
41.HashMap 和 ConcurrentHashMap 的區(qū)別
ConcurrentHashMap對整個桶數(shù)組進(jìn)行了分割分段(Segment),然后在每一個分段上都用lock鎖進(jìn)行保護(hù),相對于HashTable的synchronized 鎖的粒度更精細(xì)了一些,并發(fā)性能更好,而HashMap沒有鎖機制,不是線程安全的。(JDK1.8之后ConcurrentHashMap啟了一種全新的方式實現(xiàn),利用CAS算法。)
HashMap的鍵值對允許有null,但是ConCurrentHashMap都不允許。
42.ConcurrentHashMap 和 Hashtable 的區(qū)別?
ConcurrentHashMap 和 Hashtable 的區(qū)別主要體現(xiàn)在實現(xiàn)線程安全的方式上不同。
底層數(shù)據(jù)結(jié)構(gòu): JDK1.7的 ConcurrentHashMap 底層采用 分段的數(shù)組
+鏈表 實現(xiàn),JDK1.8 采用的數(shù)據(jù)結(jié)構(gòu)跟HashMap1.8的結(jié)構(gòu)一樣,數(shù)組+鏈表/紅黑
二叉樹。Hashtable 和 JDK1.8 之前的 HashMap 的底層數(shù)據(jù)結(jié)構(gòu)類似都是采用 數(shù)組+鏈表 的形式,數(shù)組是 HashMap 的主體,鏈表則是主要為了解決哈希沖突而存在的;
實現(xiàn)線程安全的方式(重要): ① 在JDK1.7的時候,
ConcurrentHashMap(分段鎖) 對整個桶數(shù)組進(jìn)行了分割分段(Segment),每一把鎖只鎖容器其中一部分?jǐn)?shù)據(jù),多線程訪問容器里不同數(shù)據(jù)段的數(shù)據(jù),就不會存在鎖競爭,提高并發(fā)訪問率。(默認(rèn)分配16個Segment,比Hashtable效率提高16倍。)
到了 JDK1.8 的時候已經(jīng)摒棄了Segment的概念,而是直接用
Node 數(shù)組+鏈表+紅黑樹的數(shù)據(jù)結(jié)構(gòu)來實現(xiàn),并發(fā)控制使用
synchronized 和 CAS 來操作。(JDK1.6以后 對 synchronized鎖做了很多優(yōu)化) 整個看起來就像是優(yōu)化過且線程安全的 HashMap,雖然在JDK1.8中還
能看到 Segment 的數(shù)據(jù)結(jié)構(gòu),但是已經(jīng)簡化了屬性,只是為了兼容舊版本;②
Hashtable(同一把鎖) :使用 synchronized 來保證線程安全,效率非常低下。當(dāng)一個線程訪問同步方法時,其他線程也訪問同步方法,可能會進(jìn)入阻塞或輪詢狀態(tài),如使用 put 添加元素,另一個線程不能使用 put 添加元素,也不能使用 get,競爭會越來越激烈效率越低。
兩者的對比圖:
HashTable:

JDK1.7的ConcurrentHashMap:

JDK1.8的ConcurrentHashMap(TreeBi(img)n: 紅黑二叉樹節(jié)點 Node: 鏈表節(jié)點):

答:ConcurrentHashMap 結(jié)合了 Hash(img)Map 和 HashTable 二者的優(yōu)勢。 HashMap 沒有考慮同步,HashTable 考慮了同步的問題。但是 HashTable 在每次同步執(zhí)行時都要鎖住整個結(jié)構(gòu)。 ConcurrentHashMap 鎖的方式是稍微細(xì)粒度的。
43.ConcurrentHashMap 底層具體實現(xiàn)知道嗎?實現(xiàn)原理是什么?
JDK1.7
首先將數(shù)據(jù)分為一段一段的存儲,然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個線程占用鎖訪問其中一個段數(shù)據(jù)時,其他段的數(shù)據(jù)也能被其他線程訪問。
在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式進(jìn)行實
現(xiàn),結(jié)構(gòu)如下:
一個 ConcurrentHashMap 里包含一個 Segment 數(shù)組。Segment 的結(jié)構(gòu)和 HashMap類似,是一種數(shù)組和鏈表結(jié)構(gòu),一個 Segment 包含一個 HashEntry 數(shù)組,每個 HashEntry 是一個鏈表結(jié)構(gòu)的元素,每個 Segment 守護(hù)著一個 HashEntry數(shù)組里的元素,當(dāng)對 HashEntry 數(shù)組的數(shù)據(jù)進(jìn)行修改時,必須首先獲得對應(yīng)的 Segment的鎖。
該類包含兩個靜態(tài)內(nèi)部類 HashE(img)ntry 和 Segment ;前者用來封裝映射表的鍵值對,后者用來充當(dāng)鎖的角色;
Segment 是一種可重入的鎖 ReentrantLock,每個 Segment 守護(hù)一個HashEntry 數(shù)組里得元素,當(dāng)對 HashEntry 數(shù)組的數(shù)據(jù)進(jìn)行修改時,必須首先獲得對應(yīng)的 Segment 鎖。
JDK1.8
在JDK1.8中,放棄了Segment臃腫的設(shè)計,取而代之的是采用Node + CAS + Synchronized來保證并發(fā)安全進(jìn)行實現(xiàn),synchronized只鎖定當(dāng)前鏈表或紅黑二叉樹的首節(jié)點,這樣只要hash不沖突,就不會產(chǎn)生并發(fā),效率又提升N 倍。
44.Array 和 ArrayList 有何區(qū)別?
- Array 可以存儲基本數(shù)據(jù)類型和對象,ArrayList 只能存儲對象。
- Array 是指定固定大小的,而 ArrayList 大小是自動擴(kuò)展的。
- Array 內(nèi)置方法沒有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。
對于基本類型數(shù)據(jù),集合使用自動裝箱來減少編碼工作量。但是,當(dāng)處理固定大小的基本數(shù)據(jù)類型的時候,這種方式相對比較慢。
如何實現(xiàn) Array 和 List 之間的轉(zhuǎn)換?
- Array 轉(zhuǎn) List: Arrays. asList(array) ;
- List 轉(zhuǎn) Array:List 的 toArray() 方法。
comparable 和 comparator的區(qū)別?
- comparable接口實際上是出自java.lang包,它有一個 compareTo(Object obj)方法用來排序
- comparator接口實際上是出自 java.util 包,它有一個compare(Object obj1, Object obj2)方法用來排序
一般我們需要對一個集合使用自定義排序時,我們就要重寫compareTo方法或 compare方法,當(dāng)我們需要對某一個集合實現(xiàn)兩種排序方式,比如一個song對象中的歌名和歌手名分別采用一種排序方法的話,我們可以重寫compareTo方法和使用自制的Comparator方法或者以兩個Comparator來實現(xiàn)歌名排序和歌星名排序,第二種代表我們只能使用兩個參數(shù)版的Collections.sort().
Collection 和 Collections 有什么區(qū)別?
- java.util.Collection 是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進(jìn)行基本操作的通用接口方法。Collection接口在Java 類庫中有很多具體的實現(xiàn)。Collection接口的意義是為各種具體的集合提供了大化的統(tǒng)一操作方式,其直接繼承接口有List與Set。
- Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態(tài)方法,用于對集合中元素進(jìn)行排序、搜索以及線程安全等各種操作。
45.TreeMap 和 TreeSet 在排序時如何比較元素? Collections 工具類中的 sort()方法如何比較元素?
TreeSet 要求存放的對象所屬的類必須實現(xiàn) Comparable 接口,該接口提供了比較元素的 compareTo()方法,當(dāng)插入元素時會回調(diào)該方法比較元素的大小。
TreeMap 要求存放的鍵值對映射的鍵必須實現(xiàn) Comparable 接口從而根據(jù)鍵對元素進(jìn)行排序。
Collections 工具類的 sort 方法有兩種重載的形式,
第一種要求傳入的待排序容器中存放的對象比較實現(xiàn) Comparable 接口以實現(xiàn)元素的比較;
第二種不強制性的要求容器中的元素必須可比較,但是要求傳入第二個參數(shù),參數(shù)是Comparator 接口的子類型(需要重寫 compare 方法實現(xiàn)元素的比較),相當(dāng)于一個臨時定義的排序規(guī)則,其實就是通過接口注入比較元素大小的算法,也是對回調(diào)模式的應(yīng)用(Java 中對函數(shù)式編程的支持)。
