衆所周知,HashMap是一個用於存儲Key-Value鍵值對的集合,每個鍵值對也叫Entry。這些鍵值對分散在一個數組中,這個數組就是HashMap的主幹。java
HashMap數組的每個初始值都是Null程序員
HashMap用數組+鏈表的形式解決Hash函數下index的衝突狀況,好比下面這種狀況面試
hash衝突你還知道哪些解決辦法?(1) 開放地址法(2)鏈地址法(3)再哈希法(4)公共溢出區域法算法
HashMap數組的每個元素不止是一個Entry對象,也是一個鏈表的頭節點。每個Entry對象經過Next指針指向它的下一個Entry節點。當新來的Entry映射到衝突的數組位置時,只須要插入到對應的鏈表便可。編程
因爲剛纔所說的Hash衝突,同一個位置有可能匹配到多個Entry,這時候就須要順着對應鏈表的頭節點,一個一個向下來查找。假設咱們要查找的Key是「apple」:數組
須要注意的是,新來的Entry節點插入鏈表時,使用的是「頭插法」。是由於HashMap的發明者認爲,後插入的Entry被查找的可能性更大。緩存
HashMap面試必問的6個點,你知道幾個?原做者【程序員追風】連接 基於此文作了小量摘抄和補充安全
HashMap默認的初始長度是多少?爲何這麼規定?多線程
能夠用LinkedList代替數組結構嘛? ps:Entry就是一個鏈表節點併發
意思源碼中的
Entry[] table = new Entry[capacity]
複製代碼
用
List<Entry> table = new LinkedList<Entry>();
複製代碼
表示是否可行,答案很明顯,必須是能夠
既然能夠,爲何HashMap不用LinkedList,而使用數組?
由於數組效率更高,而且採用基本數組結構,能夠本身定義擴容機制,好比HashMap中數組擴容是2的次冪,在作取模運算的時候效率高,而ArrayList的擴容機制是1.5倍擴容
HashMap在什麼條件下擴容
load factor爲0.75,爲了最大程度避免哈希衝突,也就是當load factor * current cpacity(當前數組大小)時,就要resize
爲何擴容是2的次冪?
HashMap爲了存取高效,要儘可能較少碰撞,就是要儘可能把數據分配均勻,每一個鏈表長度大體相同,這個實現就在把數據存到哪一個鏈表中的算法;這個算法實際就是取模,hash%length。
可是,你們都知道這種運算不如位移運算快。
所以,源碼中作了優化hash&(length-1)。
也就是說hash%length==hash&(length-1)
那爲何是2的n次方呢?
由於2的n次方實際就是1後面n個0,2的n次方-1,實際就是n個1。
例如長度爲8時候,3&(8-1)=3 2&(8-1)=2 ,不一樣位置上,不碰撞。
而長度爲5的時候,3&(5-1)=0 2&(5-1)=0,都在0上,出現碰撞了。
因此,保證容積是2的n次方,是爲了保證在作(length-1)的時候,每一位都能&1 ,也就是和1111……1111111進行與運算。
知道hashmap中put元素的過程是什麼樣麼?
對key的hashCode()作hash運算,計算index;
此處注意,對於key的判斷以下((k = p.key) == key || (key != null && key.equals(k)))只要知足其一就會被算做重複,而後覆蓋,也就是以下代碼,Map的大小爲1
HashMap<String,Integer> map = new HashMap(); map.put("A", 1); map.put(new String("A"), 1); System.out.println(map.size()); //大小爲1 複製代碼
若是沒碰撞直接放到bucket裏;
若是碰撞了,以鏈表的形式存在buckets後;
若是碰撞致使鏈表過長(大於等於TREEIFY_THRESHOLD),就把鏈表轉換成紅黑樹(JDK1.8中的改動);
若是節點已經存在就替換old value(保證key的惟一性)
若是bucket滿了(超過load factor*current capacity),就要resize。
hashmap中get元素的過程?
還知道哪些Hash算法?
比較出名的有MD四、MD5等
String的hashcode的實現?
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
複製代碼
String類中的hashCode計算方法仍是比較簡單的,就是以31爲權,每一位爲字符的ASCII值進行運算,用天然溢出來等效取模。
知道jdk1.8中hashmap改了啥麼?
最後一條是重點,由於最後一條的變更,hashmap在1.8中,不會在出現死循環問題。
爲何在解決hash衝突的時候,不直接用紅黑樹?而選擇先用鏈表,再轉紅黑樹?
由於紅黑樹須要進行左旋,右旋,變色這些操做來保持平衡,而單鏈表不須要。
當元素小於8個當時候,此時作查詢操做,鏈表結構已經能保證查詢性能。當元素大於8個的時候,此時須要紅黑樹來加快查詢速度,可是新增節點的效率變慢了。
所以,若是一開始就用紅黑樹結構,元素太少,新增效率又比較慢,無疑這是浪費性能的。
我不用紅黑樹,用二叉查找樹能夠麼?
能夠。可是二叉查找樹在特殊狀況下會變成一條線性結構(這就跟原來使用鏈表結構同樣了,形成很深的問題),遍歷查找會很是慢。
當鏈表轉爲紅黑樹後,何時退化爲鏈表?
爲6的時候退轉爲鏈表。中間有個差值7能夠防止鏈表和樹之間頻繁的轉換。假設一下,若是設計成鏈表個數超過8則鏈表轉換成樹結構,鏈表個數小於8則樹結構轉換成鏈表,若是一個HashMap不停的插入、刪除元素,鏈表個數在8左右徘徊,就會頻繁的發生樹轉鏈表、鏈表轉樹,效率會很低。
HashMap在併發編程環境下有什麼問題啊?
在jdk1.8中還有這些問題麼?
在jdk1.8中,死循環問題已經解決。其餘兩個問題仍是存在。
你通常怎麼解決這些問題的?
好比ConcurrentHashmap,Hashtable等線程安全等集合類。
HashMap的鍵能夠爲Null嘛
能夠
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
複製代碼
你通常用什麼做爲HashMap的key?
通常用Integer、String這種不可變類當HashMap當key,並且String最爲經常使用。
其實嚴格來講,String並非一個嚴謹的不可變類,在Java1.5之後,咱們能夠經過反射技術修改String裏的value[]數組的值。
若是讓你實現一個自定義的class做爲HashMap的key該如何實現?
此題考察兩個知識點
針對問題一,記住下面四個原則便可
(1)兩個對象相等,hashcode必定相等
(2)兩個對象不等,hashcode不必定不等
(3)hashcode相等,兩個對象不必定相等
(4)hashcode不等,兩個對象必定不等
針對問題二,記住如何寫一個不可變類
(1)類添加final修飾符,保證類不被繼承。
若是類能夠被繼承會破壞類的不可變性機制,只要繼承類覆蓋父類的方法而且繼承類能夠改變成員變量值,那麼一旦子類以父類的形式出現時,不能保證當前類是否可變。
(2)保證全部成員變量必須私有,而且加上final修飾
經過這種方式保證成員變量不可改變。但只作到這一步還不夠,由於若是是對象成員變量有可能再外部改變其值。因此第4點彌補這個不足。
(3)不提供改變成員變量的方法,包括setter
避免經過其餘接口改變成員變量的值,破壞不可變特性。
(4)經過構造器初始化全部成員,進行深拷貝(deep copy)
若是構造器傳入的對象直接賦值給成員變量,仍是能夠經過對傳入對象的修改進而致使改變內部變量的值。例如:
public final class ImmutableDemo {
private final int[] myArray;
public ImmutableDemo(int[] array) {
this.myArray = array; // wrong
}
}
複製代碼
這種方式不能保證不可變性,myArray和array指向同一塊內存地址,用戶能夠在ImmutableDemo以外經過修改array對象的值來改變myArray內部的值。
爲了保證內部的值不被修改,能夠採用深度copy來建立一個新內存保存傳入的值。正確作法:
public final class MyImmutableDemo {
private final int[] myArray;
public MyImmutableDemo(int[] array) {
this.myArray = array.clone();
}
}
複製代碼
這裏要注意,Object對象的clone()方法,實現了對象中各個屬性的複製,但它的可見範圍是protected的,因此實體類使用克隆的前提是:
① 實現Cloneable接口,這是一個標記接口,自身沒有方法。 ② 覆蓋clone()方法,可見性提高爲public。
也就是說,一個默認的clone()方法實現機制,仍然是賦值。
若是一個被複制的屬性都是基本類型,那麼只須要實現當前類的cloneable機制就能夠了,此爲淺拷貝。
若是被複制對象的屬性包含其餘實體類對象引用,那麼這些實體類對象都須要實現cloneable接口並覆蓋clone()方法。
(5)在getter方法中,不要直接返回對象自己,而是克隆對象,並返回對象的拷貝
這種作法也是防止對象外泄,防止經過getter得到內部可變成員對象後對成員變量直接操做,致使成員變量發生改變。
準備用HashMap存1w條數據,構造時傳10000還會觸發擴容嗎?
在 HashMap 中,提供了一個指定初始容量的構造方法 HashMap(int initialCapacity)
,這個方法最終會調用到 HashMap 另外一個構造方法,其中的參數 loadFactor 就是默認值 0.75f。
從構造方法的邏輯能夠看出,HashMap 並非直接使用外部傳遞進來的 initialCapacity
,而是通過了 tableSizeFor()
方法的處理,再賦值到 threshole
上。
在 tableSizeFor()
方法中,經過逐步位運算,就可讓返回值,保持在 2 的 N 次冪。以方便在擴容的時候,快速計算數據在擴容後的新表中的位置。
那麼當咱們從外部傳遞進來 1w 時,實際上通過 tableSizeFor()
方法處理以後,就會變成 2 的 14 次冪 16384,再算上負載因子 0.75f,實際在不觸發擴容的前提下,可存儲的數據容量是 12288(16384 * 0.75f)。
這種場景下,用來存放 1w 條數據,綽綽有餘了,並不會觸發咱們猜測的擴容。
概述:一般用於方法的返回值,通知調用者該方法是隻讀的,而且不指望對方修改狀態
補充:Arrays.asList返回的Arrays.ArrayList其實並不是不可變,只是add方法沒有重寫,可是能夠經過set進行下標數據交換
在Java 8咱們可使用Collections來實現返回不可變集合
方法名 | 做用 |
---|---|
List unmodifiableList(List<? extends T> list) | 返回不可變的List集合 |
Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) | 返回不可變的Map集合 |
Set unmodifiableSet(Set<? extends T> s) | 返回不可變的Set集合 |
若是咱們用Java作開發的話,最經常使用的容器之一就是List集合了,而List集合中用的較多的就是ArrayList 和 LinkedList 兩個類,這二者也常被用來作比較。由於最近在學習Java的集合類源碼,對於這兩個類天然是不能放過,因而乎,翻看他們的源碼,我發現,ArrayList實現了一個叫作 RandomAccess
的接口,而 LinkedList 是沒有的
RandomAccess 是一個標誌接口,代表實現這個這個接口的 List 集合是支持快速隨機訪問的。也就是說,實現了這個接口的集合是支持 快速隨機訪問 策略的。
同時,官網還特地說明了,若是是實現了這個接口的 List,那麼使用for循環的方式獲取數據會優於用迭代器獲取數據。
stream.of(),最好不要傳基本類型,由於 Stream主要用於對象類型的集合 ,若是傳基本類型,會將基本類型數組當作一個對象處理。
固然JDK還提供了基本類型的Stream,例如咱們能夠直接使用IntStream,而不是Stream。
集合接口
java.util.Collection 接口
通用接口 * java.util.List(有序,容許重複,容許null)
* java.util.Set(無序,不可重複,不容許null)
* Set集合底層使用hash表實現,因此不可重複,每次add的時候都會調用本身的hashCode()方法去判斷是否發生碰撞,而後是否替換等操做
* HashSet在一些特殊場景是有序的,如字母、數字
* java.util.SortedSet
* TreeSet實現了SortedSet,提供了Compare和CompareTo來定製排序
* java.util.NavigableSet(since Java 1.6)
複製代碼
Collection接口下面已細化了List,Set和Queue子接口,未什麼又定義了AbstractCollection這個抽象類?
三者都是Collection,總仍是有共同行爲的。
ArrayList集合之subList
List.sublist(20,30).clear();
一般 Set 是 Map Key 的實現,Set 底層運用 Map 實現 在HashSet中,元素都存到HashMap鍵值對的Key上面,而Value時有一個統一的值private static final Object PRESENT = new Object();,(定義一個虛擬的Object對象做爲HashMap的value,將此對象定義爲static final)
java.util.Map接口
接口 | 哈希表 | 可變數組 | 平衡樹 | 鏈表 | 哈希表+鏈表 |
---|---|---|---|---|---|
Set | HashSet | TreeSet | LinkedHashSet | ||
List | ArrayList | LinkedList | |||
Deque | ArrayDeque | LinkedList | |||
Map | HashMap | TreeMap | LinkedHashMap |
ArrayList和CopyOnWriteArrayList的區別
- CopyOnWriteArrayList
- 實現了List接口
- 內部持有一個ReentrantLock lock = new ReentrantLock();
- 底層是用volatile transient聲明的數組 array
- 讀寫分離,寫時複製出一個新的數組,完成插入、修改或者移除操做後將新數組賦值給array
- ArrayList
- 底層是數組,初始大小爲10
- 插入時會判斷數組容量是否足夠,不夠的話會進行擴容
- 所謂擴容就是新建一個新的數組,而後將老的數據裏面的元素複製到新的數組裏面
- 移除元素的時候也涉及到數組中元素的移動,刪除指定index位置的元素,而後將index+1至數組最後一個元素往前移動一個格
Vector是增刪改查方法都加了synchronized,保證同步,可是每一個方法執行的時候都要去得到鎖,性能就會大大降低,而CopyOnWriteArrayList 只是在增刪改上加鎖,可是讀不加鎖,在讀方面的性能就好於Vector,CopyOnWriteArrayList支持讀多寫少的併發狀況
Collections便利實現
接口類型