1. 程式人生 > 實用技巧 >🍖三元表示式、生成式、生成器表示式

🍖三元表示式、生成式、生成器表示式

這裡寫自定義目錄標題

此文章用來記錄hashmap的一些特點(在學習中的所瞭解的,如有不足,請指正)什麼是hash表概念先來一段百度百科的的解釋散列表(Hash table,也叫雜湊表),是根據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中一個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表。給定表M,存在函式f(key),對任意給定的關鍵字值key,代入函式後若能得到包含該關鍵字的記錄在表中的地址,則稱表M為雜湊(Hash)表,函式f(key)為雜湊(Hash) 函式。所謂的hash表在我看來嘛就是對映嘛,以前嘛要查詢一個數或者一個值嘛是通過遍歷的形式,這樣的話就會有一個問題,那就是太浪費時間了,時間效率非常低,也不能非常低嘛,時間複雜度是O(n)。於是呢,人們為了更快的找到需要查詢的值呢就想到了一種辦法,將儲存的位置與儲存的值對應起來,這樣查詢的效率不就高了很多。但是怎麼轉換呢,聰明的人類想到了一種辦法,利用一種函式對映的形式來解決,這個對映用的函式就叫做hash函式,這個表呢就叫hash散列表,但是呢這是有問題的。那就是hash表衝突很好理解嘛,不同的值可能經過hash函式生成同樣的索引,這樣的話就有衝突了,怎麼解決?請看hash表衝突的解決我所瞭解的常用的直接定址,也叫開放地址法,就是這個不能放我不放了,我放到下一個去,要是下一個還有就繼續往後直到找到可以插入的位置,要是都沒有,那就考慮一下擴容唄hash再雜湊,就是用別的hash演算法再算一遍拉鍊法,這個方法就是hashmap中用到的方法。不是有衝突嘛,統統拿來,統統放這,一個別想跑。其實就是利用連結串列,衝突了就追加節點(不是同一個的話才追加)建立公共溢位區,就是衝突了嘛,沒坑了,那就走吧,不要呆在這裡了以上就是我所瞭解的,估計也是常用的吧,不然我也不會了解HashMapmap的意思嘛,就是對映,才不是地圖。Java中的HashMap就是利用hash表加連結串列實現的K,V形式的資料結構,和python中的字典是一樣的。hashmap中的hash衝突的解決利用的是拉鍊法。1.7之前的拉鍊是隻有連結串列,而在1.8增加了一個紅黑樹結構,這是因為,當連結串列長度太長的時候查詢效率比較低。所以在hash桶資料的容量大於等於64以及hash桶內的元素數量大於等於8時就會轉換為紅黑樹。今天我們進入原始碼一探究竟,先來看個靜態常量static final int MIN_TREEIFY_CAPACITY = 64;

樹化:treeifyBinfinal void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 如果說table 為null或者說容量小於64就擴容,不執行樹化
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
// 如果說 所在的位置表不為空
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
上面的就是進行樹化的條件了,具體流程就算了吧,不看了擴容:resize()方法先準備一個重要的方法,resize()方法final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;//判斷老表是否為null為null的話長度就是0
int oldThr = threshold; // 儲存原來的老閾值
int newCap, newThr = 0; //先將新表的長度 閾值設定為0
if (oldCap > 0) {
//如果說老表的容量大於0且容量大於等於最大容量(MAXIMUM_CAPACITY = 1 << 30)
//就將閾值設為Integer.MAX_VALUE,然後直接返回也就是不再擴容了,僅僅將閾值增大就行了
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//如果說老容量乘以2小魚最大容量以及大於等於預設的容量( DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16)
// 就將原來的閾值也擴充為兩倍 就是說這裡沒啥意外容量就定下來了,也是一般的擴容情況
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//如果說老表的容量小於等於0,但是老閾值大於0,就將新的容量設定為老閾值
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//如果老表的容量以及老閾值都不大於0,就執行初始操作,將新表的容量設定為16,計算新表的閾值
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//如果說得出的新表閾值等於0的話就用新表的容量乘以負載因子,然後如果說新表的容量小於最大值以及新的閾值小於最大值,就將新閾值設為所求,否則就是Integer.MAX_VALUE
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
MAX_VALUE (int)ft : Integer.MAX_VALUE);
}
//到此用新閾值覆蓋老閾值閾值的更新操作完成
threshold = newThr;
@SuppressWarnings({“rawtypes”,“unchecked”})
//利用新的容量建立一個表(這有個問題,就是如果是兩個執行緒的話會建立兩個表)
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//新的容量覆蓋老的,容量至此也已確定
table = newTab;
//若原來的表不等於空,就進行移動,等於空的話就直接返回,因為原來的沒有東西嘛,也就不用轉移值了
if (oldTab != null) {
//這裡對老表進行遍歷,採用for迴圈
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//先將老表的值記錄下來,然後進行判空,如果不等於空的話就繼續下一步
//等於空的話也不進行任何操作
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
//在這裡看一下是不是還有下一個節點,沒有的話就計算一下新的索引所在的位置然後結束
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
//如果說e是treeNode節點,也就是說,這個hash桶裡邊的節點已經樹化過了
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//下面這個else的意思是如果有下一個節點而且沒有樹化,也是說是連結串列形式的至少有兩個節點
else { // preserve order
//這裡就是將連結串列分成兩種一種是高位連結串列一種是低位連結串列,至於什麼是高低位連結串列,咱們往下看
//loHead低位頭節點
//loTail低位尾節點
Node<K,V> loHead = null, loTail = null;
//hiHead高位頭節點
//hiTail低位尾節點
Node<K,V> hiHead = null, hiTail = null;
//next節點
Node<K,V> next;
//開始do…While迴圈,因為肯定有next節點嘛,不然也到不了這裡
do {
//儲存一下next節點
next = e.next;
//注意這個與的 是與原來的容量進行比較的沒有進行減一哈,減一是求索引用的。
//這裡的意思舉個例子來說就是比如原來的容量就是16吧,因為這裡是位運算嘛,轉換成二進位制就是10000
//因為這裡是等於0 的情況嘛,所以就假設e.hash二進位制為1011001111吧,索引算出來就是1111
//運算開始 因不足用0補齊嘛
//1011001111
// 10000
//----------
//0000000000
//嗯 就是這種情況,因為原來容量二進位制是5位也就是說如果hash值第五位是0,那麼就擴容以後不會有任何變化
//因為擴容是變為原來的2倍,也就是左移一位變為100000。
//那麼減1以後就是11111,刨去後邊的4個1,兩個最高位都是1也就是相同的,可以直接運算
//如果說此時元素的hash值在這個最高位是0的話,那麼算出的索引與原來是一樣的,這也就是低位索引
//這裡只是將低位放在一起
if ((e.hash & oldCap) == 0) {
//如果尾節點為空就初始化(說明頭節點也沒值)
if (loTail == null)
//這裡的頭節點指示頭所在的位置,以後追加就是用為節點了,高位連結串列一樣如此
loHead = e;
else
//讓尾節點的next指向e,
loTail.next = e;
//然後尾節點向後移一位
//這裡寫成loTail=loTail.next我感覺比較好理解一些
loTail = e;
}
//如果說不是0的話,說明hash值的高位是1,經過運算後就是11111就是原來的索引加上2^4
//就是原來的表的長度,所以高位連結串列只需要原來的索引加上原來的表的長度就是新的索引
//這裡只是將高位放到一起
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
//迴圈結束後,如果說低位連結串列不為空的話就說明執行了分高低位的工作,而且有低位的存在
//然後只需要將hash桶的節點指向低位連結串列的頭節點,而且因為是低位連結串列嘛,索引跟原來的一樣
if (loTail != null) {
//這裡將為節點的next設定為null,因為這再遍歷的時候尾節點的next與尾節點指向同一個位置
//因為已經遍歷完了嘛,next也就沒有值了,所以就清空。高位連結串列類似
loTail.next = null;
newTab[j] = loHead;
}
//這裡判斷高位連結串列是否為空,空的就說明沒有高位連結串列嘛
//不空的話就將原來的索引加上老表的容量,至於為什麼,上面已經解釋過
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
//至此返回新的連結串列
return newTab;
}
你以為到這裡就結束了?no,no,no,還有一個重要的方法,那就是如果是紅黑樹的話,怎麼進行操作呢,就是這個紅黑樹節點裡邊的split(this, newTab, j, oldCap)方法了final void split(HashMap<K,V> map, Node<K,V>[] tab, int index,
int bit//這個bit什麼意思呢,我猜是老表的容量,上面傳過來的oldCap
) {
TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
//開頭雷擊,夢迴連結串列
//這怎麼和連結串列的操作差不多呢,也是分成高低位
//其實註釋上面也寫了嘛
//將樹箱中的節點拆分為較高和較低的樹箱,如果現在太小,則取消樹化。
//為什麼紅黑樹的節點也可以這樣呢,因為
/**
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
// needed to unlink next upon deletion
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
*/
//所以有個成員變數是next,那不就跟連結串列一樣了
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
//這個lc吧 low count:低位的數量
//hc呢 high count:高位的數量
int lc = 0, hc = 0;
//顯而易見,這裡遍歷treeNode
//這裡為什麼不用while迴圈呢,是不用在外面宣告變數嘛
for (TreeNode<K,V> e = b, next; e != null; e = next) {
next = (TreeNode<K,V>)e.next;
e.next = null;
//這裡高低位一分,對應的count++就不展開細說了
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}

if (loHead != null) {
    if (lc <= UNTREEIFY_THRESHOLD)
        //如果說數量小於等於UNTREEIFY_THRESHOLD=6,就弄成連結串列
        tab[index] = loHead.untreeify(map);
    else {
        tab[index] = loHead;
        //如果不小於6且高位連結串列不為null就樹化
        //為啥需要高位連結串列不為空呢,
        /**
        這裡個人理解,高位連結串列如果為空,說明舊陣列下的紅黑樹中的元素在新陣列中仍然全部在同一個位置,
        且先後順序沒有改變,也就是註釋中的已經樹化了,沒有必要再次樹化;而當高位節點不為空,
        說明原連結串列元素被拆分了,且位紅黑樹節點個數大於6,不滿足轉連結串列條件,需要重新樹化。
        此處來自https://blog.csdn.net/hengwu1817/article/details/107095871/ 
        */
        //下面的高位連結串列也是如此
        if (hiHead != null) // (else is already treeified)
            loHead.treeify(tab);
    }
}
if (hiHead != null) {
    if (hc <= UNTREEIFY_THRESHOLD)
        tab[index + bit] = hiHead.untreeify(map);
    else {
        tab[index + bit] = hiHead;
        if (loHead != null)
            hiHead.treeify(tab);
    }
}

}
插入資料 put呼叫put(k,v)方法實際上呼叫的是putVal方法public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
所以只需要分析putVal方法即可final V putVal(int hash, K key, V value, boolean onlyIfAbsent,//是否不存在才插入
boolean evict//文件給的是建立表的模式,我的理解是可讀可寫
) {
Node<K,V>[] tab;
Node<K,V> p; //p是table[i]所在的頭
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;//如果說原來的表不存在或者為空就執行resize()方法,上面已經進入看了一下
//如果說原來的表的位置等於空的話就直接放進去 不存在衝突
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//這裡才是重點 到這裡說明發生衝突
Node<K,V> e;//e表示要要插入的節點
K k;
//這個判斷是看原來的老的hash值跟我傳進來的hash值是否相同並且key也相同 或者說key不為空並且相同
//也就是判斷一下是不是相同的key 是的話就將p賦值給e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//不是就判斷一下頭節點是否是紅黑樹節點
//是的話就進行紅黑樹的操作
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//不是的話就執行 也就是尋找要插入的位置的前一個節點
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果說大於等於8-1也就是7的話就樹化 因為要插入元素了嘛,所以插入以後就等於8了
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//這裡key相同的話就中斷
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//找到了要插入的節點後 如果e不為null 說明key是一樣的 只需要替換一下值就好了
if (e != null) { // existing mapping for key
//儲存一下舊的值
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
//如果不是存在就不寫的情況就換成新的值
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//記錄一下修改次數,這個有個作用就是快速失敗
++modCount;
//然後判斷是否需要擴容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
對於1.7的hashmap的死迴圈問題以及本篇文章的1.8的死迴圈資料覆蓋問題,以後再總結

歡迎使用Markdown編輯器

你好! 這是你第一次使用 Markdown編輯器 所展示的歡迎頁。如果你想學習如何使用Markdown編輯器, 可以仔細閱讀這篇文章,瞭解一下Markdown的基本語法知識。

新的改變

我們對Markdown編輯器進行了一些功能拓展與語法支援,除了標準的Markdown編輯器功能,我們增加了如下幾點新功能,幫助你用它寫部落格:

  1. 全新的介面設計 ,將會帶來全新的寫作體驗;
  2. 在創作中心設定你喜愛的程式碼高亮樣式,Markdown 將程式碼片顯示選擇的高亮樣式 進行展示;
  3. 增加了 圖片拖拽 功能,你可以將本地的圖片直接拖拽到編輯區域直接展示;
  4. 全新的 KaTeX數學公式 語法;
  5. 增加了支援甘特圖的mermaid語法1 功能;
  6. 增加了 多螢幕編輯 Markdown文章功能;
  7. 增加了 焦點寫作模式、預覽模式、簡潔寫作模式、左右區域同步滾輪設定 等功能,功能按鈕位於編輯區域與預覽區域中間;
  8. 增加了 檢查列表 功能。

功能快捷鍵

撤銷:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜體:Ctrl/Command + I
標題:Ctrl/Command + Shift + H
無序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
檢查列表:Ctrl/Command + Shift + C
插入程式碼:Ctrl/Command + Shift + K
插入連結:Ctrl/Command + Shift + L
插入圖片:Ctrl/Command + Shift + G
查詢:Ctrl/Command + F
替換:Ctrl/Command + G

合理的建立標題,有助於目錄的生成

直接輸入1次#,並按下space後,將生成1級標題。
輸入2次#,並按下space後,將生成2級標題。
以此類推,我們支援6級標題。有助於使用TOC語法後生成一個完美的目錄。

如何改變文字的樣式

強調文字 強調文字

加粗文字 加粗文字

標記文字

刪除文字

引用文字

H2O is是液體。

210 運算結果是 1024.

插入連結與圖片

連結: link.

圖片: Alt

帶尺寸的圖片: Alt

居中的圖片: Alt

居中並且帶尺寸的圖片: Alt

當然,我們為了讓使用者更加便捷,我們增加了圖片拖拽功能。

如何插入一段漂亮的程式碼片

部落格設定頁面,選擇一款你喜歡的程式碼片高亮樣式,下面展示同樣高亮的 程式碼片.

// An highlighted block
var foo = 'bar';

生成一個適合你的列表

  • 專案
    • 專案
      • 專案
  1. 專案1
  2. 專案2
  3. 專案3
  • 計劃任務
  • 完成任務

建立一個表格

一個簡單的表格是這麼建立的:

專案Value
電腦$1600
手機$12
導管$1

設定內容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文字居中第二列文字居右第三列文字居左

SmartyPants

SmartyPants將ASCII標點字元轉換為“智慧”印刷標點HTML實體。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

建立一個自定義列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何建立一個註腳

一個具有註腳的文字。2

註釋也是必不可少的

Markdown將文字轉換為 HTML

KaTeX數學公式

您可以使用渲染LaTeX數學表示式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通過尤拉積分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多關於的資訊 LaTeX 數學表示式here.

新的甘特圖功能,豐富你的文章

Mon 06 Mon 13 Mon 20 已完成 進行中 計劃一 計劃二 現有任務 Adding GANTT diagram functionality to mermaid
  • 關於 甘特圖 語法,參考 這兒,

UML 圖表

可以使用UML圖表進行渲染。 Mermaid. 例如下面產生的一個序列圖:

張三 李四 王五 你好!李四, 最近怎麼樣? 你最近怎麼樣,王五? 我很好,謝謝! 我很好,謝謝! 李四想了很長時間, 文字太長了 不適合放在一行. 打量著王五... 很好... 王五, 你怎麼樣? 張三 李四 王五

這將產生一個流程圖。:

連結 長方形 圓角長方形 菱形
  • 關於 Mermaid 語法,參考 這兒,

FLowchart流程圖

我們依舊會支援flowchart的流程圖:

Created with Raphaël 2.2.0 開始 我的操作 確認? 結束 yes no
  • 關於 Flowchart流程圖 語法,參考 這兒.

匯出與匯入

匯出

如果你想嘗試使用此編輯器, 你可以在此篇文章任意編輯。當你完成了一篇文章的寫作, 在上方工具欄找到 文章匯出 ,生成一個.md檔案或者.html檔案進行本地儲存。

匯入

如果你想載入一篇你寫過的.md檔案,在上方工具欄可以選擇匯入功能進行對應副檔名的檔案匯入,
繼續你的創作。


  1. mermaid語法說明 ↩︎

  2. 註腳的解釋 ↩︎