應廣大讀者的須要,霈哥給你們帶來新一期的乾貨啦!java
高頻面試題:BTree、紅黑樹、哈希算法分析、哈希表源碼分析
若對你和身邊的朋友有幫助, 抓緊關注 IT霈哥 點贊! 點贊! 點贊! 評論!收藏! 分享給更多的朋友共同窗習交流, 天天持續掘金離不開你的點贊支持!
簡單的理解,就是一種相似於咱們生活中樹的結構,只不過每一個結點上都最多隻能有兩個子結點。面試
二叉樹是每一個節點最多有兩個子樹的樹結構。頂上的叫根結點,兩邊被稱做「左子樹」和「右子樹」。算法
如圖:數組
咱們要說的是二叉樹的一種比較有意思的叫作紅黑樹,紅黑樹自己就是一顆二叉查找樹,將節點插入後,該樹仍然是一顆二叉查找樹。markdown
如圖:app
紅黑樹能夠經過紅色節點和黑色節點儘量的保證二叉樹的平衡,從而來提升效率。函數
紅黑樹的約束:源碼分析
- 節點能夠是紅色的或者黑色的。
- 根節點是黑色的。
- 葉子節點(特指空節點)是黑色的。
- 每一個紅色節點的子節點都是黑色的。
- 任何一個節點到其每個葉子節點的全部路徑上黑色節點數相同。
紅黑樹的特色:post
速度特別快,趨近平衡樹。性能
java.lang.Object
類中定義了方法:public int hashCode()
返回對象的哈希碼值,任何類都繼承Object,也都會擁有此方法。
定義Person類,不添加任何成員,直接調用Person對象的hashCode()方法,執行Object類的hashCode():
public class Person{}
複製代碼
測試類
public static void main(String[] args){
Person person = new Person();
int code = person.hashCode();
}
複製代碼
看到運行結果,就是一個int類型的整數,若是將這個整數轉爲十六進制就看到所謂的對象地址值,可是他不是地址值,咱們將他稱爲對象的哈希值。
Person類重寫hashCode()方法:直接返回0
public int hashCode(){
return 0;
}
複製代碼
運行後,方法將執行Person類的重寫方法,結果爲0,屬於Person類自定義對象的哈希值,而沒有使用父類Object類方法hashCode()。
public static void main(String[] args){
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
複製代碼
程序分析:兩個字符串對象都是採用new關鍵字建立的,s1==s2的結果爲false,可是s1,s2兩個對象的哈希值倒是相同的,均爲96354,緣由是String類繼承Object,重寫了父類方法hashCode()創建本身的哈希值。
字符串底層實現是字符數組,被final修飾,一旦建立不能修改。
private final char value[];
複製代碼
定義字符串「abc」或者是new String("abc"),都會轉成char value[]數組存儲,長度爲3。
/* * 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;
}
複製代碼
- 第一次循環: h = 31 * h + val[0],h = 31 * 0 + 97結果:h = 97。
- 第二次循環: h = 31 * 97 + val[1],h = 31 * 97 + 98結果:h = 3105。
- 第三次循環: h = 31 * 3105 + val[2],h = 31 * 3105 + 99結果:h = 96354。
- return 96354。
在JDK1.8以前,哈希表底層採用數組+鏈表實現,即便用數組處理衝突,同一hash值的鏈表都存儲在一個數組裏。可是當位於一個桶中的元素較多,即hash值相等的元素較多時,經過key值依次查找的效率較低。而JDK1.8中,哈希表存儲採用數組+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減小了查找時間。
簡單的來講,哈希表是由數組+鏈表+紅黑樹(JDK1.8增長了紅黑樹部分)實現的,以下圖所示。
總而言之,JDK1.8引入紅黑樹大程度優化了HashMap的性能,那麼對於咱們來說保證HashSet集合元素的惟一,其實就是根據對象的hashCode和equals方法來決定的。若是咱們往集合中存放自定義的對象,那麼保證其惟一,就必須複寫hashCode和equals方法創建屬於當前對象的比較方式。
HashSet集合自己並不提供功能,底層依賴HashMap集合,HashSet構造方法中建立了HashMap對象:
map = new HashMap<>();
複製代碼
所以源代碼分析是分析HashMap集合的源代碼,HashSet集合中的add方法調研的是HashMap集合中的put方法。
//HashMap中的靜態成員變量
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
複製代碼
解析:使用無參數構造方法建立HashMap對象,將加載因子設置爲默認的加載因子,loadFactor=0.75F。
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);
}
複製代碼
解析帶有參數構造方法,傳遞哈希表的初始化容量和加載因子
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;
}
複製代碼
解析該方法對咱們傳遞的初始化容量進行位移運算,位移的結果是 8 4 2 1 碼
哈希表是採用數組+鏈表的實現方法,HashMap中的內部類Node很是重要
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
複製代碼
解析內部類Node中具備4個成員變量
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
複製代碼
解析put方法中調研putVal方法,putVal方法中調用hash方法。
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
複製代碼
解析方法中進行Node對象數組的判斷,若是數組是null或者長度等於0,那麼就會調研resize()方法進行數組的擴容。
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
複製代碼
解析計算結果,新的數組容量=原始數組容量<<1,也就是乘以2。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
複製代碼
解析:i = (數組長度 - 1) & 對象的哈希值,會獲得一個索引,而後在此索引下tab[i],建立鏈表對象。
不一樣哈希值的對象,也是有可能存儲在同一個數組索引下。
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
複製代碼
解析若是對象的哈希值相同,對象的equals方法返回true,判斷爲一個對象,進行覆蓋操做。
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
複製代碼
解析若是對象哈希值相同,可是對象的equals方法返回false,將對此鏈表進行遍歷,當鏈表沒有下一個節點的時候,建立下一個節點存儲對象。
後續連載文章, 敬請觀看:
觀看更多精品文章,請移步至 大廠必用技術彙總篇!🔥
若對你和身邊的朋友有幫助, 抓緊關注 IT霈哥 點贊! 點贊! 點贊! 評論!收藏! 分享給更多的朋友共同窗習交流, 天天持續更新離不開你的支持!
歡迎關注個人B站,未來會發布文章同步視頻~~~