經典問題:這三者的區別
HashMap的put的邏輯
閥值默認8,最低樹化容量:64
HashMap 的長度為什么是2的冪次方
Hash 值的范圍值-2147483648到2147483647,一個40億長度的數組,內存是放不下的,用之前還要先做對數組的長度取模運算,得到的余數才能用來要存放的位置也就是對應的數組下標。這個數組下標的計算方法是“ (n - 1) & hash”。(n代表數組長度)。這也就解釋了 HashMap 的長度為什么是2的冪次方。“取余(%)操作中如果除數是2的冪次則等價于與其除數減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。
length = 16 16 -1 = 15 15二進制 1111 再相與(&)hash效果很好
如何有效減少碰撞
擾動函數:促使元素位置分布均勻,減少碰撞幾率(hash函數保證了同時包含高16位和低16位的特性,使得更加的不確定性)
使用final對象,并采用合適的equals()和hashcode(),如String Integer ,因為hashcode不會變
追問,為什么若重寫equals(Object obj)方法,就必須重寫hashcode()方法,確保通過equals(Object obj)方法判斷結果為true的兩個對象具備相等的hashcode()返回值?
- 如果重寫了equals(),兩個對象判斷相等了。但是沒有重寫hashCode(),有可能兩對象相等卻hashCode不同,則HashSet這種存唯一值的可能會存了兩個一樣的對象。
為什么使用HashCode?
- 通過hashCode比較比equals方法快,當get時先比較hashCode,如果hashCode不同,直接返回false。
hash函數
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
圖示:
1.高16bt不變,低16bit和高16bit做了一個異或(得到的hashcode轉化為32位的二進制,前16位和后16位低16bit和高16bit做了一個異或)
2.(n-1)&hash=->得到下標
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
HashMap也可以這樣寫實現線程安全:
Map safeHashMap = Collections.synchronizedMap(HashMap);
但是和Hashtable一樣,都是串行的,效率低下。
concurrentHashMap key和Value不可以為null
CAS樂觀鎖,synchronized悲觀鎖
concurrentHashMap 比java 8之前的 segment(分段加鎖),鎖更細
首先使用無鎖操作CAS插入頭節點,失敗則循環重試
若頭節點已存在,則嘗試獲取頭節點的同步鎖,再進行操作
size方法和mappingCount()方法的異同,兩者計算是否準確
JDK1.7 和 JDK1.8 對 size 的計算是不一樣的。 1.7 中是先不加鎖計算三次,如果三次結果不一樣在加鎖。
JDK1.8 size 是通過對 baseCount 和 counterCell 進行 CAS 計算,最終通過 baseCount 和 遍歷 CounterCell 數組得出 size。
JDK 8 推薦使用mappingCount 方法,因為這個方法的返回值是 long 類型,不會因為 size 方法是 int 類型限制最大值。
size()最大值是 Integer 類型的最大值,但是 Map 的 size 可能超過 MAX_VALUE, 所以還有一個方法 mappingCount(),JDK 的建議使用 mappingCount() 而不是size()
原文鏈接:https://blog.csdn.net/qq_27037443/article/details/94441885
多線程下環境如何進行擴容
TreeMap 是有序的散列表,它是通過紅黑樹實現的。它一般用于單線程中存儲有序的映射。(了解一下)
手撕一個Hashmap(無紅黑樹)
首先,接口類
public interface BtyMap<K,V> {
public V put(K k,V v);
public V get(K k);
interface Entry<K,V>{
public K getKey();
public V getValue();
}
}
具體實現類:
public class ButyHashMap<K,V> implements BtyMap<K, V>{
private Entry<K, V>[] arr = null;
int size;
public ButyHashMap() {
arr = new Entry[16];
}
public int size() {
return this.size;
}
class Entry<K,V> implements BtyMap.Entry<K, V>{
private K key;
private V value;
private Entry<K, V> next;
public Entry(){
}
public Entry(K key, V value,Entry<K, V> next) {
this.key = key;
this.value = value;
this.next=next;
}
@Override
public K getKey(){return key;}
@Override
public V getValue(){return value;}
}
@Override
public V put(K key, V value) {
V oldValue= null;
int index = key.hashCode() % arr.length;
if (arr[index] == null) {
arr[index] = new Entry<K,V>(key, value,null);
size++;
} else {
Entry<K, V> entry=arr[index];
Entry<K, V> e = entry;
while(e != null){
if(key == e.getKey()||key.equals(e.getValue())){
oldValue = e.value;
e.value = value;
return oldValue;
}
e = e.next;
}
arr[index] = new Entry(key, value, entry);
size++;
}
return oldValue;
}
@Override
public V get(K key){
int index=key.hashCode() % arr.length;
if(arr[index]==null)
return null;
else{
Entry<K, V> entry = arr[index];
while(entry!=null){
if(key == entry.getKey()||key.equals(entry.getKey())){
return entry.value;
}
entry=entry.next;
}
}
return null;
}
}
紅黑樹規則
紅黑樹本質上還是二叉查找樹,額外的引入了5個約束條件:
1.節點只能是紅色或黑色
2.根節點必須是黑色
3.所有NIL節點都是黑色的
4.一條路徑上不能出現相鄰的兩個紅色節點
5.在任何遞歸子樹內,根節點到葉子節點的所有路徑上包含相同數目的黑色節點
紅黑樹的插入與旋轉
那些年,面試被虐過的紅黑樹