「面試必問」BTree、紅黑樹、哈希算法分析、哈希表源碼解析

前言:

應廣大讀者的須要,霈哥給你們帶來新一期的乾貨啦!java

高頻面試題:BTree、紅黑樹、哈希算法分析、哈希表源碼分析

若對你和身邊的朋友有幫助, 抓緊關注 IT霈哥 點贊! 點贊! 點贊! 評論!收藏! 分享給更多的朋友共同窗習交流, 天天持續掘金離不開你的點贊支持!

第一章 BTree、紅黑樹

1.1 BTree

  • 二叉樹binary tree ,是每一個結點不超過2的有序樹(tree)

簡單的理解,就是一種相似於咱們生活中樹的結構,只不過每一個結點上都最多隻能有兩個子結點。面試

二叉樹是每一個節點最多有兩個子樹的樹結構。頂上的叫根結點,兩邊被稱做「左子樹」和「右子樹」。算法

如圖:數組

1.2 紅黑樹

咱們要說的是二叉樹的一種比較有意思的叫作紅黑樹,紅黑樹自己就是一顆二叉查找樹,將節點插入後,該樹仍然是一顆二叉查找樹。markdown

如圖:app

紅黑樹能夠經過紅色節點和黑色節點儘量的保證二叉樹的平衡,從而來提升效率。函數

紅黑樹的約束:源碼分析

  1. 節點能夠是紅色的或者黑色的。
  2. 根節點是黑色的。
  3. 葉子節點(特指空節點)是黑色的。
  4. 每一個紅色節點的子節點都是黑色的。
  5. 任何一個節點到其每個葉子節點的全部路徑上黑色節點數相同。

紅黑樹的特色:post

速度特別快,趨近平衡樹。性能

第二章 哈希算法分析、哈希表源碼分析

2.1 對象的哈希值

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()。

2.2 String對象的哈希值

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()創建本身的哈希值。

String類的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;
}
複製代碼

String的哈希算法分析

  • int h = hash,hash是成員變量,默認值0,int h = 0。
  • 判斷h==0爲true && value.length>0,數組value的長度爲3,保存三個字符abc,判斷結果總體爲true。
  • char val[] = value,將value數組賦值給val數組。
  • for循環3次,將value數組進行遍歷,取出abc三個字符。
  • 第一次循環: 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。
  • 算法:31 * 上一次的哈希值+字符的ASCII碼值,31屬於質數,每次乘以31是爲了下降字符串不一樣,可是計算出相同哈希值的機率。

2.3 哈希表

什麼是哈希表呢?

JDK1.8以前,哈希表底層採用數組+鏈表實現,即便用數組處理衝突,同一hash值的鏈表都存儲在一個數組裏。可是當位於一個桶中的元素較多,即hash值相等的元素較多時,經過key值依次查找的效率較低。而JDK1.8中,哈希表存儲採用數組+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減小了查找時間。

  • 哈希表的初始化容量,數組長度爲16個
    • 當數組容量不夠時,擴容爲原數組長度的2倍
  • 加載因子爲0.75
    • 指示當數組的容量被使用到長度的75%時,進行擴容。

簡單的來講,哈希表是由數組+鏈表+紅黑樹(JDK1.8增長了紅黑樹部分)實現的,以下圖所示。

2.4 哈希表存儲字符對象的過程圖

總而言之,JDK1.8引入紅黑樹大程度優化了HashMap的性能,那麼對於咱們來說保證HashSet集合元素的惟一,其實就是根據對象的hashCode和equals方法來決定的。若是咱們往集合中存放自定義的對象,那麼保證其惟一,就必須複寫hashCode和equals方法創建屬於當前對象的比較方式。

2.5 哈希表源代碼分析

HashSet集合自己並不提供功能,底層依賴HashMap集合,HashSet構造方法中建立了HashMap對象:

map = new HashMap<>();
複製代碼

所以源代碼分析是分析HashMap集合的源代碼,HashSet集合中的add方法調研的是HashMap集合中的put方法。

HashMap無參數構造方法的分析

//HashMap中的靜態成員變量
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
複製代碼

解析:使用無參數構造方法建立HashMap對象,將加載因子設置爲默認的加載因子,loadFactor=0.75F。

HashMap有參數構造方法分析

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);
}
複製代碼

解析帶有參數構造方法,傳遞哈希表的初始化容量和加載因子

  • 若是initialCapacity(初始化容量)小於0,直接拋出異常。
  • 若是initialCapacity大於最大容器,initialCapacity直接等於最大容器
    • MAXIMUM_CAPACITY = 1 << 30 是最大容量 (1073741824)
  • 若是loadFactor(加載因子)小於等於0,直接拋出異常
  • tableSizeFor(initialCapacity)方法計算哈希表的初始化容量。
    • 注意:哈希表是進行計算得出的容量,而初始化容量不直接等於咱們傳遞的參數。

tableSizeFor方法分析

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 碼

  • 例如傳遞2,結果仍是2,傳遞的是4,結果仍是4。
  • 例如傳遞3,結果是4,傳遞5,結果是8,傳遞20,結果是32。

Node 內部類分析

哈希表是採用數組+鏈表的實現方法,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個成員變量

  • hash,對象的哈希值
  • key,做爲鍵的對象
  • value,做爲值得對象(講解Set集合,不牽扯值得問題)
  • next,下一個節點對象

存儲元素的put方法源碼

public V put(K key, V value) {
	return putVal(hash(key), key, value, false, true);
}
複製代碼

解析put方法中調研putVal方法,putVal方法中調用hash方法。

  • hash(key)方法:傳遞要存儲的元素,獲取對象的哈希值
  • putVal方法,傳遞對象哈希值和要存儲的對象key

putVal方法源碼

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()方法進行數組的擴容。

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站,未來會發布文章同步視頻~~~

相關文章
相關標籤/搜索