剖析面試最常見問題之Java基礎(chǔ)知識
說說List,Set,Map三者的區(qū)別?
- List(對付順序的好幫手): List接口存儲一組不唯一(可以有多個(gè)元素引用相同的對象),有序的對象
- Set(注重獨(dú)一無二的性質(zhì)): 不允許重復(fù)的集合。不會有多個(gè)元素引用相同的對象。
- Map(用Key來搜索的專家): 使用鍵值對存儲。Map會維護(hù)與Key有關(guān)聯(lián)的值。兩個(gè)Key可以引用相同的對象,但Key不能重復(fù),典型的Key是String類型,但也可以是任何對象。
Arraylist 與 LinkedList 區(qū)別?
1. 是否保證線程安全:
ArrayList和LinkedList都是不同步的,也就是不保證線程安全;2. 底層數(shù)據(jù)結(jié)構(gòu):
Arraylist底層使用的是Object數(shù)組;LinkedList底層使用的是 雙向鏈表 數(shù)據(jù)結(jié)構(gòu)(JDK1.6之前為循環(huán)鏈表,JDK1.7取消了循環(huán)。注意雙向鏈表和雙向循環(huán)鏈表的區(qū)別,下面有介紹到!)3. 插入和刪除是否受元素位置的影響: ①
ArrayList采用數(shù)組存儲,所以插入和刪除元素的時(shí)間復(fù)雜度受元素位置的影響。 比如:執(zhí)行add(E e)方法的時(shí)候,ArrayList會默認(rèn)在將指定的元素追加到此列表的末尾,這種情況時(shí)間復(fù)雜度就是O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element))時(shí)間復(fù)雜度就為 O(n-i)。因?yàn)樵谶M(jìn)行上述操作的時(shí)候集合中第 i 和第 i 個(gè)元素之后的(n-i)個(gè)元素都要執(zhí)行向后位/向前移一位的操作。 ②LinkedList采用鏈表存儲,所以插入,刪除元素時(shí)間復(fù)雜度不受元素位置的影響,都是近似 O(1)而數(shù)組為近似 O(n)。4. 是否支持快速隨機(jī)訪問:
LinkedList不支持高效的隨機(jī)元素訪問,而ArrayList支持??焖匐S機(jī)訪問就是通過元素的序號快速獲取元素對象(對應(yīng)于get(int index)方法)。5. 內(nèi)存空間占用: ArrayList的空 間浪費(fèi)主要體現(xiàn)在在list列表的結(jié)尾會預(yù)留一定的容量空間,而LinkedList的空間花費(fèi)則體現(xiàn)在它的每一個(gè)元素都需要消耗比ArrayList更多的空間(因?yàn)橐娣胖苯雍罄^和直接前驅(qū)以及數(shù)據(jù))。
補(bǔ)充內(nèi)容:RandomAccess接口
public interface RandomAccess {
}
查看源碼我們發(fā)現(xiàn)實(shí)際上 RandomAccess 接口中什么都沒有定義。所以,在我看來 RandomAccess 接口不過是一個(gè)標(biāo)識罷了。標(biāo)識什么? 標(biāo)識實(shí)現(xiàn)這個(gè)接口的類具有隨機(jī)訪問功能。
在 binarySearch()方法中,它要判斷傳入的list 是否 RamdomAccess 的實(shí)例,如果是,調(diào)用indexedBinarySearch()方法,如果不是,那么調(diào)用iteratorBinarySearch()方法
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
ArrayList 實(shí)現(xiàn)了 RandomAccess 接口, 而 LinkedList 沒有實(shí)現(xiàn)。為什么呢?我覺得還是和底層數(shù)據(jù)結(jié)構(gòu)有關(guān)!ArrayList 底層是數(shù)組,而 LinkedList 底層是鏈表。數(shù)組天然支持隨機(jī)訪問,時(shí)間復(fù)雜度為 O(1),所以稱為快速隨機(jī)訪問。鏈表需要遍歷到特定位置才能訪問特定位置的元素,時(shí)間復(fù)雜度為 O(n),所以不支持快速隨機(jī)訪問。,ArrayList 實(shí)現(xiàn)了 RandomAccess 接口,就表明了他具有快速隨機(jī)訪問功能。 RandomAccess 接口只是標(biāo)識,并不是說 ArrayList 實(shí)現(xiàn) RandomAccess 接口才具有快速隨機(jī)訪問功能的!
下面再總結(jié)一下 list 的遍歷方式選擇:
- 實(shí)現(xiàn)了
RandomAccess接口的list,優(yōu)先選擇普通 for 循環(huán) ,其次 foreach, - 未實(shí)現(xiàn)
RandomAccess接口的list,優(yōu)先選擇iterator遍歷(foreach遍歷底層也是通過iterator實(shí)現(xiàn)的,),大size的數(shù)據(jù),千萬不要使用普通for循環(huán)
補(bǔ)充內(nèi)容:雙向鏈表和雙向循環(huán)鏈表
雙向鏈表: 包含兩個(gè)指針,一個(gè)prev指向前一個(gè)節(jié)點(diǎn),一個(gè)next指向后一個(gè)節(jié)點(diǎn)。

雙向循環(huán)鏈表: 最后一個(gè)節(jié)點(diǎn)的 next 指向head,而 head 的prev指向最后一個(gè)節(jié)點(diǎn),構(gòu)成一個(gè)環(huán)。

ArrayList 與 Vector 區(qū)別呢?為什么要用Arraylist取代Vector呢?
Vector類的所有方法都是同步的。可以由兩個(gè)線程安全地訪問一個(gè)Vector對象、但是一個(gè)線程訪問Vector的話代碼要在同步操作上耗費(fèi)大量的時(shí)間。
Arraylist不是同步的,所以在不需要保證線程安全時(shí)時(shí)建議使用Arraylist。
說一說 ArrayList 的擴(kuò)容機(jī)制吧
詳見筆主的這篇文章:通過源碼一步一步分析ArrayList 擴(kuò)容機(jī)制
HashMap 和 Hashtable 的區(qū)別
-
線程是否安全: HashMap 是非線程安全的,HashTable 是線程安全的;HashTable 內(nèi)部的方法基本都經(jīng)過
synchronized修飾。(如果你要保證線程安全的話就使用 ConcurrentHashMap 吧!); - 效率: 因?yàn)榫€程安全的問題,HashMap 要比 HashTable 效率高一點(diǎn)。另外,HashTable 基本被淘汰,不要在代碼中使用它;
- 對Null key 和Null value的支持: HashMap 中,null 可以作為鍵,這樣的鍵只有一個(gè),可以有一個(gè)或多個(gè)鍵所對應(yīng)的值為 null。。但是在 HashTable 中 put 進(jìn)的鍵值只要有一個(gè) null,直接拋出 NullPointerException。
-
初始容量大小和每次擴(kuò)充容量大小的不同 : ①創(chuàng)建時(shí)如果不指定容量初始值,Hashtable 默認(rèn)的初始大小為11,之后每次擴(kuò)充,容量變?yōu)樵瓉淼?n+1。HashMap 默認(rèn)的初始化大小為16。之后每次擴(kuò)充,容量變?yōu)樵瓉淼?倍。②創(chuàng)建時(shí)如果給定了容量初始值,那么 Hashtable 會直接使用你給定的大小,而 HashMap 會將其擴(kuò)充為2的冪次方大小(HashMap 中的
tableSizeFor()方法保證,下面給出了源代碼)。也就是說 HashMap 總是使用2的冪作為哈希表的大小,后面會介紹到為什么是2的冪次方。 - 底層數(shù)據(jù)結(jié)構(gòu): JDK1.8 以后的 HashMap 在解決哈希沖突時(shí)有了較大的變化,當(dāng)鏈表長度大于閾值(默認(rèn)為8)時(shí),將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時(shí)間。Hashtable 沒有這樣的機(jī)制。
HasMap 中帶有初始容量的構(gòu)造函數(shù):
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
下面這個(gè)方法保證了 HashMap 總是使用2的冪作為哈希表的大小。
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
HashMap 和 HashSet區(qū)別
如果你看過 HashSet 源碼的話就應(yīng)該知道:HashSet 底層就是基于 HashMap 實(shí)現(xiàn)的。(HashSet 的源碼非常非常少,因?yàn)槌?clone()、writeObject()、readObject()是 HashSet 自己不得不實(shí)現(xiàn)之外,其他方法都是直接調(diào)用 HashMap 中的方法。
| HashMap | HashSet |
|---|---|
| 實(shí)現(xiàn)了Map接口 | 實(shí)現(xiàn)Set接口 |
| 存儲鍵值對 | 僅存儲對象 |
調(diào)用 put()向map中添加元素 |
調(diào)用 add()方法向Set中添加元素 |
| HashMap使用鍵(Key)計(jì)算Hashcode | HashSet使用成員對象來計(jì)算hashcode值,對于兩個(gè)對象來說hashcode可能相同,所以equals()方法用來判斷對象的相等性, |
HashSet如何檢查重復(fù)
當(dāng)你把對象加入HashSet時(shí),HashSet會先計(jì)算對象的hashcode值來判斷對象加入的位置,同時(shí)也會與其他加入的對象的hashcode值作比較,如果沒有相符的hashcode,HashSet會假設(shè)對象沒有重復(fù)出現(xiàn)。但是如果發(fā)現(xiàn)有相同hashcode值的對象,這時(shí)會調(diào)用equals()方法來檢查hashcode相等的對象是否真的相同。如果兩者相同,HashSet就不會讓加入操作成功。(摘自我的Java啟蒙書《Head fist java》第二版)
hashCode()與equals()的相關(guān)規(guī)定:
- 如果兩個(gè)對象相等,則hashcode一定也是相同的
- 兩個(gè)對象相等,對兩個(gè)equals方法返回true
- 兩個(gè)對象有相同的hashcode值,它們也不一定是相等的
- 綜上,equals方法被覆蓋過,則hashCode方法也必須被覆蓋
- hashCode()的默認(rèn)行為是對堆上的對象產(chǎn)生獨(dú)特值。如果沒有重寫hashCode(),則該class的兩個(gè)對象無論如何都不會相等(即使這兩個(gè)對象指向相同的數(shù)據(jù))。
==與equals的區(qū)別
- ==是判斷兩個(gè)變量或?qū)嵗遣皇侵赶蛲粋€(gè)內(nèi)存空間 equals是判斷兩個(gè)變量或?qū)嵗赶虻膬?nèi)存空間的值是不是相同
- ==是指對內(nèi)存地址進(jìn)行比較 equals()是對字符串的內(nèi)容進(jìn)行比較
- ==指引用是否相同 equals()指的是值是否相同
HashMap的底層實(shí)現(xiàn)
JDK1.8之前
JDK1.8 之前 HashMap 底層是 數(shù)組和鏈表 結(jié)合在一起使用也就是 鏈表散列。HashMap 通過 key 的 hashCode 經(jīng)過擾動函數(shù)處理過后得到 hash 值,然后通過 (n - 1) & hash 判斷當(dāng)前元素存放的位置(這里的 n 指的是數(shù)組的長度),如果當(dāng)前位置存在元素的話,就判斷該元素與要存入的元素的 hash 值以及 key 是否相同,如果相同的話,直接覆蓋,不相同就通過拉鏈法解決沖突。
所謂擾動函數(shù)指的就是 HashMap 的 hash 方法。使用 hash 方法也就是擾動函數(shù)是為了防止一些實(shí)現(xiàn)比較差的 hashCode() 方法 換句話說使用擾動函數(shù)之后可以減少碰撞。
JDK 1.8 HashMap 的 hash 方法源碼:
JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加簡化,但是原理不變。
static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^ :按位異或
// >>>:無符號右移,忽略符號位,空位都以0補(bǔ)齊
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
對比一下 JDK1.7的 HashMap 的 hash 方法源碼.
static int hash(int h) {
// 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);
}
相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能會稍差一點(diǎn)點(diǎn),因?yàn)楫吘箶_動了 4 次。
所謂 “拉鏈法” 就是:將鏈表和數(shù)組相結(jié)合。也就是說創(chuàng)建一個(gè)鏈表數(shù)組,數(shù)組中每一格就是一個(gè)鏈表。若遇到哈希沖突,則將沖突的值加到鏈表中即可。

JDK1.8之后
相比于之前的版本, JDK1.8之后在解決哈希沖突時(shí)有了較大的變化,當(dāng)鏈表長度大于閾值(默認(rèn)為8)時(shí),將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時(shí)間。

TreeMap、TreeSet以及JDK1.8之后的HashMap底層都用到了紅黑樹。紅黑樹就是為了解決二叉查找樹的缺陷,因?yàn)槎娌檎覙湓谀承┣闆r下會退化成一個(gè)線性結(jié)構(gòu)。
推薦閱讀:
- 《Java 8系列之重新認(rèn)識HashMap》 :https://zhuanlan.zhihu.com/p/21673805
HashMap 的長度為什么是2的冪次方
為了能讓 HashMap 存取高效,盡量較少碰撞,也就是要盡量把數(shù)據(jù)分配均勻。我們上面也講到了過了,Hash 值的范圍值-2147483648到2147483647,前后加起來大概40億的映射空間,只要哈希函數(shù)映射得比較均勻松散,一般應(yīng)用是很難出現(xiàn)碰撞的。但問題是一個(gè)40億長度的數(shù)組,內(nèi)存是放不下的。所以這個(gè)散列值是不能直接拿來用的。用之前還要先做對數(shù)組的長度取模運(yùn)算,得到的余數(shù)才能用來要存放的位置也就是對應(yīng)的數(shù)組下標(biāo)。這個(gè)數(shù)組下標(biāo)的計(jì)算方法是“ (n - 1) & hash”。(n代表數(shù)組長度)。這也就解釋了 HashMap 的長度為什么是2的冪次方。
這個(gè)算法應(yīng)該如何設(shè)計(jì)呢?
我們首先可能會想到采用%取余的操作來實(shí)現(xiàn)。但是,重點(diǎn)來了:“取余(%)操作中如果除數(shù)是2的冪次則等價(jià)于與其除數(shù)減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)?!?/strong> 并且 采用二進(jìn)制位操作 &,相對于%能夠提高運(yùn)算效率,這就解釋了 HashMap 的長度為什么是2的冪次方。
HashMap 多線程操作導(dǎo)致死循環(huán)問題
主要原因在于 并發(fā)下的Rehash 會造成元素之間會形成一個(gè)循環(huán)鏈表。不過,jdk 1.8 后解決了這個(gè)問題,但是還是不建議在多線程下使用 HashMap,因?yàn)槎嗑€程下使用 HashMap 還是會存在其他問題比如數(shù)據(jù)丟失。并發(fā)環(huán)境下推薦使用 ConcurrentHashMap 。
詳情請查看:https://coolshell.cn/articles/9606.html
ConcurrentHashMap 和 Hashtable 的區(qū)別
ConcurrentHashMap 和 Hashtable 的區(qū)別主要體現(xiàn)在實(shí)現(xiàn)線程安全的方式上不同。
- 底層數(shù)據(jù)結(jié)構(gòu): JDK1.7的 ConcurrentHashMap 底層采用 分段的數(shù)組+鏈表 實(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 的主體,鏈表則是主要為了解決哈希沖突而存在的;
- 實(shí)現(xiàn)線程安全的方式(重要): ① 在JDK1.7的時(shí)候,ConcurrentHashMap(分段鎖) 對整個(gè)桶數(shù)組進(jìn)行了分割分段(Segment),每一把鎖只鎖容器其中一部分?jǐn)?shù)據(jù),多線程訪問容器里不同數(shù)據(jù)段的數(shù)據(jù),就不會存在鎖競爭,提高并發(fā)訪問率。 到了 JDK1.8 的時(shí)候已經(jīng)摒棄了Segment的概念,而是直接用 Node 數(shù)組+鏈表+紅黑樹的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn),并發(fā)控制使用 synchronized 和 CAS 來操作。(JDK1.6以后 對 synchronized鎖做了很多優(yōu)化) 整個(gè)看起來就像是優(yōu)化過且線程安全的 HashMap,雖然在JDK1.8中還能看到 Segment 的數(shù)據(jù)結(jié)構(gòu),但是已經(jīng)簡化了屬性,只是為了兼容舊版本;② Hashtable(同一把鎖) :使用 synchronized 來保證線程安全,效率非常低下。當(dāng)一個(gè)線程訪問同步方法時(shí),其他線程也訪問同步方法,可能會進(jìn)入阻塞或輪詢狀態(tài),如使用 put 添加元素,另一個(gè)線程不能使用 put 添加元素,也不能使用 get,競爭會越來越激烈效率越低。
兩者的對比圖:
圖片來源:http://www.cnblogs.com/chengxiao/p/6842045.html
HashTable:

JDK1.7的ConcurrentHashMap:

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

ConcurrentHashMap線程安全的具體實(shí)現(xiàn)方式/底層具體實(shí)現(xiàn)
JDK1.7(上面有示意圖)
首先將數(shù)據(jù)分為一段一段的存儲,然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個(gè)線程占用鎖訪問其中一個(gè)段數(shù)據(jù)時(shí),其他段的數(shù)據(jù)也能被其他線程訪問。
ConcurrentHashMap 是由 Segment 數(shù)組結(jié)構(gòu)和 HashEntry 數(shù)組結(jié)構(gòu)組成。
Segment 實(shí)現(xiàn)了 ReentrantLock,所以 Segment 是一種可重入鎖,扮演鎖的角色。HashEntry 用于存儲鍵值對數(shù)據(jù)。
static class Segment<K,V> extends ReentrantLock implements Serializable {
}
一個(gè) ConcurrentHashMap 里包含一個(gè) Segment 數(shù)組。Segment 的結(jié)構(gòu)和HashMap類似,是一種數(shù)組和鏈表結(jié)構(gòu),一個(gè) Segment 包含一個(gè) HashEntry 數(shù)組,每個(gè) HashEntry 是一個(gè)鏈表結(jié)構(gòu)的元素,每個(gè) Segment 守護(hù)著一個(gè)HashEntry數(shù)組里的元素,當(dāng)對 HashEntry 數(shù)組的數(shù)據(jù)進(jìn)行修改時(shí),必須首先獲得對應(yīng)的 Segment的鎖。
JDK1.8 (上面有示意圖)
ConcurrentHashMap取消了Segment分段鎖,采用CAS和synchronized來保證并發(fā)安全。數(shù)據(jù)結(jié)構(gòu)跟HashMap1.8的結(jié)構(gòu)類似,數(shù)組+鏈表/紅黑二叉樹。Java 8在鏈表長度超過一定閾值(8)時(shí)將鏈表(尋址時(shí)間復(fù)雜度為O(N))轉(zhuǎn)換為紅黑樹(尋址時(shí)間復(fù)雜度為O(long(N)))
synchronized只鎖定當(dāng)前鏈表或紅黑二叉樹的首節(jié)點(diǎn),這樣只要hash不沖突,就不會產(chǎn)生并發(fā),效率又提升N倍。
comparable 和 Comparator的區(qū)別
- comparable接口實(shí)際上是出自java.lang包 它有一個(gè)
compareTo(Object obj)方法用來排序 - comparator接口實(shí)際上是出自 java.util 包它有一個(gè)
compare(Object obj1, Object obj2)方法用來排序
一般我們需要對一個(gè)集合使用自定義排序時(shí),我們就要重寫compareTo()方法或compare()方法,當(dāng)我們需要對某一個(gè)集合實(shí)現(xiàn)兩種排序方式,比如一個(gè)song對象中的歌名和歌手名分別采用一種排序方法的話,我們可以重寫compareTo()方法和使用自制的Comparator方法或者以兩個(gè)Comparator來實(shí)現(xiàn)歌名排序和歌星名排序,第二種代表我們只能使用兩個(gè)參數(shù)版的 Collections.sort().
Comparator定制排序
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(-1);
arrayList.add(3);
arrayList.add(3);
arrayList.add(-5);
arrayList.add(7);
arrayList.add(4);
arrayList.add(-9);
arrayList.add(-7);
System.out.println("原始數(shù)組:");
System.out.println(arrayList);
// void reverse(List list):反轉(zhuǎn)
Collections.reverse(arrayList);
System.out.println("Collections.reverse(arrayList):");
System.out.println(arrayList);
// void sort(List list),按自然排序的升序排序
Collections.sort(arrayList);
System.out.println("Collections.sort(arrayList):");
System.out.println(arrayList);
// 定制排序的用法
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
System.out.println("定制排序后:");
System.out.println(arrayList);
Output:
原始數(shù)組:
[-1, 3, 3, -5, 7, 4, -9, -7]
Collections.reverse(arrayList):
[-7, -9, 4, 7, -5, 3, 3, -1]
Collections.sort(arrayList):
[-9, -7, -5, -1, 3, 3, 4, 7]
定制排序后:
[7, 4, 3, 3, -1, -5, -7, -9]
重寫compareTo方法實(shí)現(xiàn)按年齡來排序
// person對象沒有實(shí)現(xiàn)Comparable接口,所以必須實(shí)現(xiàn),這樣才不會出錯,才可以使treemap中的數(shù)據(jù)按順序排列
// 前面一個(gè)例子的String類已經(jīng)默認(rèn)實(shí)現(xiàn)了Comparable接口,詳細(xì)可以查看String類的API文檔,另外其他
// 像Integer類等都已經(jīng)實(shí)現(xiàn)了Comparable接口,所以不需要另外實(shí)現(xiàn)了
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(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;
}
/**
* TODO重寫compareTo方法實(shí)現(xiàn)按年齡來排序
*/
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
if (this.age > o.getAge()) {
return 1;
} else if (this.age < o.getAge()) {
return -1;
}
return age;
}
}
public static void main(String[] args) {
TreeMap<Person, String> pdata = new TreeMap<Person, String>();
pdata.put(new Person("張三", 30), "zhangsan");
pdata.put(new Person("李四", 20), "lisi");
pdata.put(new Person("王五", 10), "wangwu");
pdata.put(new Person("小紅", 5), "xiaohong");
// 得到key的值的同時(shí)得到key所對應(yīng)的值
Set<Person> keys = pdata.keySet();
for (Person key : keys) {
System.out.println(key.getAge() + "-" + key.getName());
}
}
Output:
5-小紅
10-王五
20-李四
30-張三
集合框架底層數(shù)據(jù)結(jié)構(gòu)總結(jié)
Collection
1. List
- Arraylist: Object數(shù)組
- Vector: Object數(shù)組
- LinkedList: 雙向鏈表(JDK1.6之前為循環(huán)鏈表,JDK1.7取消了循環(huán))
2. Set
- HashSet(無序,唯一): 基于 HashMap 實(shí)現(xiàn)的,底層采用 HashMap 來保存元素
- LinkedHashSet: LinkedHashSet 繼承與 HashSet,并且其內(nèi)部是通過 LinkedHashMap 來實(shí)現(xiàn)的。有點(diǎn)類似于我們之前說的LinkedHashMap 其內(nèi)部是基于 Hashmap 實(shí)現(xiàn)一樣,不過還是有一點(diǎn)點(diǎn)區(qū)別的。
- TreeSet(有序,唯一): 紅黑樹(自平衡的排序二叉樹。)
Map
- HashMap: JDK1.8之前HashMap由數(shù)組+鏈表組成的,數(shù)組是HashMap的主體,鏈表則是主要為了解決哈希沖突而存在的(“拉鏈法”解決沖突)。JDK1.8以后在解決哈希沖突時(shí)有了較大的變化,當(dāng)鏈表長度大于閾值(默認(rèn)為8)時(shí),將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時(shí)間
- LinkedHashMap: LinkedHashMap 繼承自 HashMap,所以它的底層仍然是基于拉鏈?zhǔn)缴⒘薪Y(jié)構(gòu)即由數(shù)組和鏈表或紅黑樹組成。另外,LinkedHashMap 在上面結(jié)構(gòu)的基礎(chǔ)上,增加了一條雙向鏈表,使得上面的結(jié)構(gòu)可以保持鍵值對的插入順序。同時(shí)通過對鏈表進(jìn)行相應(yīng)的操作,實(shí)現(xiàn)了訪問順序相關(guān)邏輯。詳細(xì)可以查看:《LinkedHashMap 源碼詳細(xì)分析(JDK1.8)》
- Hashtable: 數(shù)組+鏈表組成的,數(shù)組是 HashMap 的主體,鏈表則是主要為了解決哈希沖突而存在的
- TreeMap: 紅黑樹(自平衡的排序二叉樹)
如何選用集合?
主要根據(jù)集合的特點(diǎn)來選用,比如我們需要根據(jù)鍵值獲取到元素值時(shí)就選用Map接口下的集合,需要排序時(shí)選擇TreeMap,不需要排序時(shí)就選擇HashMap,需要保證線程安全就選用ConcurrentHashMap.當(dāng)我們只需要存放元素值時(shí),就選擇實(shí)現(xiàn)Collection接口的集合,需要保證元素唯一時(shí)選擇實(shí)現(xiàn)Set接口的集合比如TreeSet或HashSet,不需要就選擇實(shí)現(xiàn)List接口的比如ArrayList或LinkedList,然后再根據(jù)實(shí)現(xiàn)這些接口的集合的特點(diǎn)來選用。