最近參加了一下頭條後端工程師的面試, 很慘, 一面就掛掉了.java
回來以後也對面試過程作了一些總結,就不夾帶私貨了,這篇文章主要對面試過程當中的技術問題作一個覆盤.node
這是一道 LeetCode 原題, 原題連接面試
求二叉樹的最遠距離節點間的節點. 首先總結幾個規律:redis
因此咱們要作:後端
代碼以下:安全
public int diameterOfBinaryTree(TreeNode root) {
AtomicReference<Integer> ret = new AtomicReference<>(0);
find(root, ret);
return ret.get();
}
private int find(TreeNode node, AtomicReference<Integer> result) {
if (node == null) return 0;
int left = 0, right = 0;
if (node.left != null) left = find(node.left,result) + 1;
if (node.right != null) right = find(node.right,result) + 1;
int tmp = Math.max(result.get(), left + right);
result.set(tmp);
return Math.max(left, right);
}
複製代碼
代碼很簡單, 就是遞歸的求節點的左子樹最遠葉子和右子樹最遠葉子. 而後在 計算過程當中, 將 當前節點的直徑
做爲一個備選項存儲,最後求最大直徑便可.服務器
Java在1.5添加了自動裝箱和拆箱機制. 總的來講基本就是基本類型和對應的包裝類型之間的自動轉換.微信
以下面的代碼中:併發
public class BoxTest {
public static void main(String [] args){
Integer a = 10; // 裝箱
int b = a; // 拆箱
}
}
複製代碼
咱們將代碼編譯以後進行反編譯, 能夠看到函數
很明顯在 代碼中的 #2
,#3
處進行了裝箱和拆箱.
分別調用了Integer的 valueOf
方法和intValue
方法.
這裏不對全部的垃圾收集器展開講解, 有興趣的朋友們能夠移步 JVM的數據區域與垃圾收集.
衆所周知, CMS的垃圾收集過程以下:
因此在初始標記和從新標記兩個階段仍是須要暫停用戶線程的.
查看ConcurrentHashMap
的源碼能夠發現, Node節點的定義是:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
}
複製代碼
能夠看到, 裏面定義了幾個屬性, 分別以下:
在get(Ojbect)
方法的調用過程當中.
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//獲取hash值
int h = spread(key.hashCode());
//經過tabat獲取hash桶, tabAt是一個線程安全的操做, 有UnSafe來保證的.
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//若是該hash桶的第一個節點就是查找結果,則返回
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
//第一個節點是樹的根節點,按照樹的方式進行遍歷查找
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
//第一個節點是鏈表的根節點,按照鏈表的方式遍歷查找
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
複製代碼
在這個過程當中,
tabAt
來操做, 線程安全.因此 get
過程當中不用加鎖也能夠正確的獲取對象.
這個問題比較寬泛,我我的的理解有如下兩點.
hashmap的rehash過程想必你們都是瞭解的, 那麼這麼稍微說一下redis的漸進式hash.
首先, rehash是要將原來表中的全部數據從新hash一遍,存放到新的表格中, 以進行擴容.
而redis是一個單線程的高性能的服務, 若是一個hash表中有幾億條數據, rehash 花費的時間將比較長, 而在此期間, redis是沒法對外提供服務的, 這是不可接受的.
所以, redis實現了漸進式hash. 過程以下:
rehashindex
位置上的值rehash到ht[1]上. 將 rehashindex 遞增一位.在上面的過程當中有兩個問題沒有提到:
解決辦法是: 在redis的定時函數裏, 也加入幫助rehash的操做, 這樣子若是服務器空閒, 就會比較快的完成rehash.
解決辦法: 對於添加操做, 直接添加到ht[1]上, 所以這樣才能保證ht[0]的數量只會減小不會增長,才能保證rehash過程能夠完結. 而刪除,修改, 查詢等操做會在ht[0]上進行, 若是得不到結果, 會去ht[1]再執行一遍.
漸進式hash帶來的好處是顯而易見的, 他採用了分而治之的思想, 將rehash操做分散到每個對該哈希表的操做上,避免了集中式rehash帶來的性能壓力.
與此同時,漸進式hash也帶來了一個問題, 那就是 在rehash的時間內, 須要保存兩個 hash表, 對內存的佔用稍大, 並且若是在redis服務器原本內存滿了的時候, 忽然進行rehash會形成大量的key被拋棄.
《Redis設計與實現(第二版》
完。
以上皆爲我的所思所得,若有錯誤歡迎評論區指正。
歡迎轉載,煩請署名並保留原文連接。
聯繫郵箱:huyanshi2580@gmail.com
更多學習筆記見我的博客或關注微信公衆號 < 呼延十 > ------>呼延十