HashMap 源碼學習-jdk1.8

1、一些常量的定義

這里針對MIN_TREEIFY_CAPACITY 這個值進行解釋一下。

java8里面,HashMap 的數據結構是數組 + (鏈表或者紅黑樹),每個數組節點下可能會存在鏈表和紅黑樹之間的轉換,當同一個索引下面的節點超過8個時,首先會看當前數組長度,如果大于64,則會發生鏈表向紅黑樹的 轉換,否則不會轉換,而是擴容。

    // 默認的初始化長度 16static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16// 默認的最大容量 2^30static final int MAXIMUM_CAPACITY = 1 << 30;// 默認的擴容因子static final float DEFAULT_LOAD_FACTOR = 0.75f;// 鏈表轉為樹的閾值static final int TREEIFY_THRESHOLD = 8;// 樹轉為鏈表的閾值static final int UNTREEIFY_THRESHOLD = 6;static final int MIN_TREEIFY_CAPACITY = 64;// map已存節點的數量transient int size;// 修改次數    transient int modCount;// 擴容閾值 當size達到這個值的時候,hashmap開始擴容int threshold;// 加載因子 threshold = 容量 * loadFactorfinal float loadFactor;

2、構造器

HashMap提供了三個構造器。

    // 無參構造器,使用默認加載因子 0.75public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}// 只傳入初始化容量,也會使用默認加載因子 0.75public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}// 同時傳入初始化容量和加載因子 (初始化容量要大于0,且不能超過最大容量)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;// 初始化的容量先賦值給了threshold 暫存。this.threshold = tableSizeFor(initialCapacity);}

注意看,使用帶參構造器 會調用 tableSizeFor(initialCapacity); 這個方法是干嘛的呢?其實就是為了計算初始化容量。HashMap規定,其容量必須是2的N次方

  • ?不傳初始化容量,就取默認值16
  • 傳了初始化容量,則初始化容量設置為大于等于該數值的 一個最小的2的N次方
    • 比如傳入了7,不是2的N次方,那么取比他大的最小的2的N次方,就是8
    • 比如傳入了8,剛好是2的N次方,那就取8
    • 比如傳入了9,不是2的N次方,那么取比他大的最小的2的N次方,就是16
    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;}

或等于操作?a |= b ,其實就是?a = a | b。

無符號右移操作:a >>> b 就表示將a向右移動b位,左邊空出來的用0補充,右邊的被丟棄

那么?n |= n >>> 1 操作得到的結果就是,最高位和次高位的結果為1;--- > n 的前兩位為1

n |= n >>> 2 操作之后 --- > n的前四位為1

.... 一通操作之后,得到的值是一個低位全是1的值。然后返回的時候+1,得到的值就是一個比n大的2的N次方。而開頭的?int n = cap - 1 是為了解決本身就是2的N次方的場景。

3、插入操作
3.1、插入操作的具體流程
  1. 插入前首先判斷數組是否為空,如果為空就進行初始化
  2. 計算key的hash值,然后和數組長度-1 進行 & 運算,獲取在數組中的索引位置
    1. 當前位置不存在元素,就直接創建新節點放在當前索引位置
    2. 當前位置元素存在,就走后續的邏輯
  3. 判斷當前坐標下頭節點的hash值是否和 key的hash相等,如果相等就進行替換(還要判斷一個控參 onlyIfAbsent,這個為false的時候才會替換,最常用的put操作這個值就是false?)
  4. 如果不相等,判斷當前是鏈表還是紅黑樹
    1. 如果是鏈表,遍歷鏈表節點,并統計節點個數:
      1. 如果找到了相同的key,就進行覆蓋操作,
      2. 如果沒有找到相同key,就將節點添加到鏈表最后面,并判斷是否超過8個節點,如果大于等于8,就要鏈表轉紅黑樹操作。
    2. 如果是紅黑樹:找到紅黑樹根節點,從根節點開始遍歷:
      1. 找到相同的key,就進行替換
      2. 找不到相同的key,就放到相應的位置,然后進行紅黑樹插入平衡調整
  5. 插入完成之后,判斷當前節點數目是否超過擴容閾值,如果超過,就進行擴容。
public V put(K key, V value) {/*** 首先計算出了key的hash 值*/return putVal(hash(key), key, value, false, true);
}final V putVal ( int hash, K key, V value,boolean onlyIfAbsent, boolean evict){HashMap.Node<K, V>[] tab;HashMap.Node<K, V> p;int n, i;/*** 判斷數組是否為空,為空則進行數組初始化 * ---> tab = resize() 然后獲取數組的長度*/if ((tab = table) == null || (n = tab.length) == 0) {n = (tab = resize()).length;}/*** 計算當前節點要插入數組的索引的位置 ---> (n - 1) & hash* 如果索引處不存在節點,就新創建節點放到索引的位置*/if ((p = tab[i = (n - 1) & hash]) == null) {tab[i] = newNode(hash, key, value, null);}/*** 如果索引處存在節點,走這個邏輯*/else {HashMap.Node<K, V> e;K k;if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {/*** 進入這個分支,說明要插入的節點和頭節點的key相同*/e = p;} else if (p instanceof HashMap.TreeNode) {/*** 說明頭節點是紅黑樹了,要把這個新節點插入到紅黑樹中,涉及到新節點的插入,紅黑樹的平衡調整等*/e = ((HashMap.TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);} else {/*** 說明頭節點是鏈表節點,遍歷鏈表*/for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {/*** 遍歷到最后了,創建新節點插入到尾端* 還要判斷節點是否超過8個,超過了要轉化為紅黑樹*/p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st{treeifyBin(tab, hash);}break;}if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {/*** 找到了相同key的value*/break;}p = e;}}/*** e不為空,說明有key相同的情況,替換成新的value,然后直接返回舊的節點* 因為節點數目不存在變化,因此不需要進行擴容判斷*/if (e != null) { // existing mapping for keyV oldValue = e.value;// onlyIfAbsent的判斷if (!onlyIfAbsent || oldValue == null) {e.value = value;}afterNodeAccess(e);return oldValue;}}++modCount;/*** 如果當前節點超過了擴容閾值,就進行擴容,然后返回null*/if (++size > threshold) {resize();}afterNodeInsertion(evict);return null;
}

3.2、 key的hash值是怎么計算的?為什么要這么計算?
  • 如果key為空,就直接返回0
  • 不為空將 key的hashcode 和 hashcode左移16位進行& 運算
  • ----?左移16位主要就是為了將hash的高位也參與到hash計算中,減少hash沖突。
    static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}

3.3、resize擴容流程介紹
  1. 首先會對老數組進行一系列的校驗,大致分為:
    1. 老數組為空,就設置一下數組長度和擴容閾值,新建數組,然后返回
    2. 老數組不為空,校驗老數組長度,如果長度超過上限,擴容閾值修改為int最大值,返回
    3. 否則:容量、擴容閾值變為原來的2倍
  2. 接著開始遍歷老數組
    1. 當前坐標下沒有節點,就繼續遍歷
    2. 當前坐標只有一個節點,計算hash值,然后放到新數組對應位置
    3. 當前坐標是鏈表,走鏈表邏輯:
      1. 遍歷鏈表節點,計算??e.hash & oldCap ,這個值如果是0,說明擴容后,在新數組的坐標和老數組一樣,如果為1 ,說明擴容后在新數組的坐標應該是 老數組坐標 + 擴容長度,因此通過計算這個值,可以將鏈表節點分為高位節點和低位節點
      2. 定義高位和低位兩個鏈表,不斷將鏈表節點放在這兩個新鏈表尾端
      3. 然后低位鏈表放在新數組的i 坐標位置,高位鏈表放在新數組i+oldcap的位置
    4. 當前坐標是紅黑樹,走紅黑樹的邏輯
      1. 因為維護紅黑樹的時候也維護了一個雙向鏈表,因此通過 e.prev e.next就可以遍歷整個樹 (也就是說遍歷鏈表就等于遍歷樹)
      2. 同樣是將元素分別放在低位鏈表和高位鏈表中,并計算每個鏈表的長度
      3. 低位鏈表的頭節點放在新數組的i坐標位置,然后維護鏈表的紅黑樹結構(維護前會判斷高位鏈表是否有值,如果為空,說明樹結構沒有被破壞而是直接遷移到新數組中了,這個時候就可以不用重新維護樹結構了)
      4. 高位鏈表頭節點放在新數組i+oldcap的位置,維護樹結構,同3

注意:jdk1.8中,hashmap的擴容,鏈表節點處理只遍歷了一次,而ConcurrentHashMap中遍歷了兩次。

final HashMap.Node<K, V>[] resize() {HashMap.Node<K, V>[] oldTab = table;// 臨時存儲老的數組長度和老的擴容閾值int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;// 定義新的數組長度和新的擴容閾值int newCap, newThr = 0;// oldCap > 0 說明數組已經初始化了if (oldCap > 0) {// 當前數組長度已經大于等于最大數組長度了,就把擴容閾值設置為int最大值返回,不需要擴容了if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}// 否則,長度變為原來2倍else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {newThr = oldThr << 1; // double threshold}} else if (oldThr > 0) // initial capacity was placed in threshold{newCap = oldThr;} else {               // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {float ft = (float) newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE);}// hashmap 初始化的時候,是將數組初始化長度賦值給了threshold,這里開始才是變成擴容閾值。threshold = newThr;// 創建新的數組,并將新數組賦值給tableHashMap.Node<K, V>[] newTab = (HashMap.Node<K, V>[]) new HashMap.Node[newCap];table = newTab;// 老數組不為空,就走擴容邏輯,否則就直接返回新創建的數組了if (oldTab != null) {// 對老數組開始遍歷for (int j = 0; j < oldCap; ++j) {HashMap.Node<K, V> e;// 數組的坐標節點為空說明沒數據,直接遍歷下個坐標if ((e = oldTab[j]) != null) {oldTab[j] = null;// 只有個節點,直接取出該節點,計算hash值,放到新數組中if (e.next == null) {newTab[e.hash & (newCap - 1)] = e;}// 當前是紅黑樹,執行紅黑樹擴容邏輯else if (e instanceof HashMap.TreeNode) {((HashMap.TreeNode<K, V>) e).split(this, newTab, j, oldCap);}// 當前是鏈表,執行鏈表擴容邏輯else { // preserve order// 定義高位鏈表和低位鏈表HashMap.Node<K, V> loHead = null, loTail = null;HashMap.Node<K, V> hiHead = null, hiTail = null;HashMap.Node<K, V> next;// 遍歷鏈表do {next = e.next;// e.hash & oldCap 可以計算出當前節點應該放在高位還是低位if ((e.hash & oldCap) == 0) {// 將遍歷到的節點放在loTail尾部// loHead指向低位節點的頭節點if (loTail == null) {loHead = e;} else {loTail.next = e;}loTail = e;} else {// 將遍歷到的節點放在hiTail尾部// hiHead指向高位節點的頭節點if (hiTail == null) {hiHead = e;} else {hiTail.next = e;}hiTail = e;}} while ((e = next) != null);// 低位鏈表的頭節點放在 新數組的原index中if (loTail != null) {loTail.next = null;newTab[j] = loHead;}// 高位鏈表的頭節點放在 新數組的原index + oldCap 中if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;
}final void split(HashMap<K, V> map, HashMap.Node<K, V>[] tab, int index, int bit) {// ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 這個this 就是數組中取出來的第一個元素,也就是樹的頭節點HashMap.TreeNode<K, V> b = this;// 設置低位首節點和低位尾節點,高位首節點和高位尾節點HashMap.TreeNode<K, V> loHead = null, loTail = null;HashMap.TreeNode<K, V> hiHead = null, hiTail = null;// 這兩個值用于記錄低位坐標和高位坐標節點的數目int lc = 0, hc = 0;// 從根節點開始,對整個樹進行遍歷,我們介紹了,紅黑樹其實也維護了雙向鏈表,因此通過 e.prev  e.next就可以遍歷整個樹for (HashMap.TreeNode<K, V> e = b, next; e != null; e = next) {next = (HashMap.TreeNode<K, V>) e.next;e.next = null;// bit傳入的就是oldCap,也就是舊數組的長度,通過hash & 運算,就可以判斷是放在新數組的低位坐標還是高位坐標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) {// 低位節點數目小于等于6,就轉為鏈表if (lc <= UNTREEIFY_THRESHOLD) {tab[index] = loHead.untreeify(map);}// 否則,還是紅黑樹結構else {// 鏈表頭節點賦值給 tab[index]tab[index] = loHead;if (hiHead != null)// 對低位的鏈表維護紅黑樹結構// 為什么加一個hiHead != null 判斷呢?因為如果原來的元素全部都分到了低位節點,那說明樹結構沒有被破壞,就不需要維護了{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);}}}
}

3.4 鏈表轉紅黑樹

final void treeifyBin(HashMap.Node<K, V>[] tab, int hash) {int n, index;HashMap.Node<K, V> e;// 如果數組為空,或者數組長度小于64,就先嘗試擴容,因為鏈表轉樹的消耗太大了if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) {resize();} // 先拿到當前坐標下的頭節點 ,賦值給 eelse if ((e = tab[index = (n - 1) & hash]) != null) {// 定義頭節點和尾節點HashMap.TreeNode<K, V> hd = null, tl = null;// 遍歷鏈表do {// 將鏈表節點轉化為紅黑樹節點HashMap.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);// 截止到目前,把鏈表中所有的node對象轉變為了紅黑樹節點,單向鏈表變成了雙向鏈表// 把轉換后的雙向鏈表,替換原來位置上的單向鏈表if ((tab[index] = hd) != null) {// 樹化操作hd.treeify(tab);}}
}final void treeify(HashMap.Node<K, V>[] tab) {HashMap.TreeNode<K, V> root = null;// 因為是調用的hd.treeify(tab),因此,這里的this就是雙向鏈表的頭節點,這里先賦值給了臨時變量x// 開始循環這個雙向鏈表了,x就是循環的元素,next就是下一個節點元素for (HashMap.TreeNode<K, V> x = this, next; x != null; x = next) {next = (HashMap.TreeNode<K, V>) x.next;// 當前節點左右孩子都設置為空x.left = x.right = null;if (root == null) {// 第一次進來,根節點肯定是空,將頭節點設置為根節點,染色黑x.parent = null;x.red = false;root = x;} // 第一次以后的循環都走下面的分支了else {// 定義當前節點的key 和 hashK k = x.key;int h = x.hash;Class<?> kc = null;// 開始遍歷樹結構了for (HashMap.TreeNode<K, V> p = root; ; ) {// ph 和 pk 定義當前樹節點的 hash 和 key ,通過hash判斷當前節點要放在樹的左邊還是右邊// dir代表 往樹左邊放還是右邊放int dir, ph;K pk = p.key;if ((ph = p.hash) > h) {dir = -1;} else if (ph < h) {dir = 1;} // hash相等的時候,繼續一系列的判斷,最終得到direlse if ((kc == null && (kc = comparableClassFor(k)) == null)|| (dir = compareComparables(kc, k, pk)) == 0) {dir = tieBreakOrder(k, pk);}HashMap.TreeNode<K, V> xp = p;// dir <= 0 說明是在左側,否則是在右側// 只有保證當前樹節點沒有對應的左孩子或者右孩子的時候,才會將當前節點掛上去,否則繼續循環遍歷樹結構if ((p = (dir <= 0) ? p.left : p.right) == null) {x.parent = xp;if (dir <= 0) {xp.left = x;} else {xp.right = x;}// 紅黑樹平衡操作root = balanceInsertion(root, x);// 當前節點已經插入紅黑樹中了,可以跳出當前循環,遍歷鏈表的下一個節點break;}}}}// 把root節點放在當前坐標位置moveRootToFront(tab, root);
}/*** 我們要明確,紅黑樹節點不但維護了樹結構,還維護了雙向鏈表的結構* 這個方法的作用就是:* 1、將樹的根節點,賦值給tab[i]* 2、將這個節點,變成雙向鏈表的頭節點*/
static <K, V> void moveRootToFront(HashMap.Node<K, V>[] tab, HashMap.TreeNode<K, V> root) {int n;if (root != null && tab != null && (n = tab.length) > 0) {// 通過根節點 hash計算在數組中的索引位置int index = (n - 1) & root.hash;// 取到當前索引的第一個節點HashMap.TreeNode<K, V> first = (HashMap.TreeNode<K, V>) tab[index];// 如果root節點和 當前索引位置第一個節點不一樣,就把root節點放在當前坐標位置// 同時要維護雙向鏈表,將root節點變成雙向鏈表的第一個節點。if (root != first) {HashMap.Node<K, V> rn;tab[index] = root;// 將root節點變成雙向鏈表的第一個節點。HashMap.TreeNode<K, V> rp = root.prev;if ((rn = root.next) != null) {((HashMap.TreeNode<K, V>) rn).prev = rp;}if (rp != null) {rp.next = rn;}if (first != null) {first.prev = root;}root.next = first;root.prev = null;}assert checkInvariants(root);}
}
4、刪除操作
  1. 數組沒有初始化,或者對應下標節點為空,說明沒有該元素,直接返回null
  2. 查找node,(紅黑樹或者鏈表結構)
  3. 刪除node,(紅黑樹或者鏈表結構) --- 刪除節點的時候即便樹的元素小于等于6也不會轉為鏈表,在代碼里面沒看到,只在擴容的時候會有轉換操作。
/*** 刪除方法,主要介紹以下兩個參數* @param matchValue true:只有值也相同的時候才刪除* @param movable true:刪除后移動節點,樹結構的時候會用到,一般為true*/
final HashMap.Node<K, V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) {HashMap.Node<K, V>[] tab;HashMap.Node<K, V> p;int n, index;// 組數沒有初始化,或者對應坐標下面沒有元素,直接返回null了if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) {// node記錄要刪除的元素HashMap.Node<K, V> node = null, e;K k;V v;// 找要刪除的元素,賦值給nodeif (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {node = p;} else if ((e = p.next) != null) {if (p instanceof HashMap.TreeNode) {// 從樹中查找節點node = ((HashMap.TreeNode<K, V>) p).getTreeNode(hash, key);} else {// 從鏈表中查找節點 ,鏈表結構時,p是node的前置節點do {if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {node = e;break;}p = e;} while ((e = e.next) != null);}}if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) {// node不為空的時候,刪除節點if (node instanceof HashMap.TreeNode) {((HashMap.TreeNode<K, V>) node).removeTreeNode(this, tab, movable);} else if (node == p) {tab[index] = node.next;} else {p.next = node.next;}// 修改次數加1,size減一,返回刪除的node++modCount;--size;afterNodeRemoval(node);return node;}}return null;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/696158.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/696158.shtml
英文地址,請注明出處:http://en.pswp.cn/news/696158.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Webpack】處理字體圖標和音視頻資源

處理字體圖標資源 1. 下載字體圖標文件 打開阿里巴巴矢量圖標庫open in new window選擇想要的圖標添加到購物車&#xff0c;統一下載到本地 2. 添加字體圖標資源 src/fonts/iconfont.ttf src/fonts/iconfont.woff src/fonts/iconfont.woff2 src/css/iconfont.css 注意字體…

[計算機網絡]---TCP協議

前言 作者&#xff1a;小蝸牛向前沖 名言&#xff1a;我可以接受失敗&#xff0c;但我不能接受放棄 如果覺的博主的文章還不錯的話&#xff0c;還請點贊&#xff0c;收藏&#xff0c;關注&#x1f440;支持博主。如果發現有問題的地方歡迎?大家在評論區指正 目錄 一 、TCP協…

Java并發基礎:原子類之AtomicBoolean全面解析

本文概要 AtomicBoolean類優點在于能夠確保布爾值在多線程環境下的原子性操作&#xff0c;避免了繁瑣的同步措施&#xff0c;它提供了高效的非阻塞算法實現&#xff0c;可以大大提成程序的并發性能&#xff0c;AtomicBoolean的API設計非常簡單易用。 AtomicBoolean核心概念 …

P1024 [NOIP2001 提高組] 一元三次方程求解

P1024 [NOIP2001 提高組] 一元三次方程求解 純代碼記錄 #include <iostream> #include <math.h> using namespace std; double a,b,c,d; double res[3];//用于存放三個解 int resCount;inline double F(double x)//三次函數 {return a*pow(x,3)b*pow(x,2)c*xd; }//…

web前端開發this指向問題

? 函數內部中的 this 指向誰&#xff0c;不是在函數定義時決定的&#xff0c;而是在函數第一次調用并執行的時候決定的 1. call 方法 語法&#xff1a;函數名.call(調用者, 參數1, …) 作用&#xff1a;函數被借用時&#xff0c;會立即執行&#xff0c;并且函數體內的this會…

Facebook Horizon:探索虛擬現實中的社交空間

隨著科技的不斷進步&#xff0c;虛擬現實&#xff08;VR&#xff09;技術正成為社交互動和娛樂體驗的新前沿。在這個數字時代&#xff0c;Facebook作為全球最大的社交媒體平臺之一&#xff0c;正在引領虛擬社交的新時代&#xff0c;其推出的虛擬社交平臺Facebook Horizon成為了…

Tomcat線程池原理(下篇:工作原理)

文章目錄 前言正文一、執行線程的基本流程1.1 JUC中的線程池執行線程1.2 Tomcat 中線程池執行線程 二、被改造的阻塞隊列2.1 TaskQueue的 offer(...)2.2 TaskQueue的 force(...) 三、總結 前言 Tomcat 線程池&#xff0c;是依據 JUC 中的線程池 ThreadPoolExecutor 重新自定義…

深入理解C語言(5):程序環境和預處理詳解

文章主題&#xff1a;程序環境和預處理詳解&#x1f30f;所屬專欄&#xff1a;深入理解C語言&#x1f4d4;作者簡介&#xff1a;更新有關深入理解C語言知識的博主一枚&#xff0c;記錄分享自己對C語言的深入解讀。&#x1f606;個人主頁&#xff1a;[?]的個人主頁&#x1f3c4…

Imagewheel私人圖床搭建結合內網穿透實現無公網IP遠程訪問教程

文章目錄 1.前言2. Imagewheel網站搭建2.1. Imagewheel下載和安裝2.2. Imagewheel網頁測試2.3.cpolar的安裝和注冊 3.本地網頁發布3.1.Cpolar臨時數據隧道3.2.Cpolar穩定隧道&#xff08;云端設置&#xff09;3.3.Cpolar穩定隧道&#xff08;本地設置&#xff09; 4.公網訪問測…

flutter 文件上傳組件和大文件分片上傳

文件分片上傳 資料 https://www.cnblogs.com/caijinglong/p/11558389.html 使用分段上傳來上傳和復制對象 - Amazon Simple Storage Service 因為公司使用的是亞馬遜的s3桶 下面是查閱資料獲得的 亞馬遜s3桶的文件上傳分片 分段上分為三個步驟&#xff1a;開始上傳、上傳對…

CSP-J 2023 T3 一元二次方程

文章目錄 題目題目背景題目描述輸入格式輸出格式樣例 #1樣例輸入 #1樣例輸出 #1 提示 題目傳送門題解思路總代碼 提交結果尾聲 題目 題目背景 眾所周知&#xff0c;對一元二次方程 a x 2 b x c 0 , ( a ≠ 0 ) ax ^ 2 bx c 0, (a \neq 0) ax2bxc0,(a0)&#xff0c;可…

STM32G030C8T6:定時器1ms中斷(以64MHz外部晶振為例)

本專欄記錄STM32開發各個功能的詳細過程&#xff0c;方便自己后續查看&#xff0c;當然也供正在入門STM32單片機的兄弟們參考&#xff1b; 本小節的目標是&#xff0c;系統主頻64 MHZ,采用高速外部晶振&#xff0c;通過定時器3 每秒中斷控制 PB9 引腳輸出高低電平&#xff0c;從…

20240222作業

完善對話框&#xff0c;點擊登錄對話框&#xff0c;如果賬號和密碼匹配&#xff0c;則彈出信息對話框&#xff0c;給出提示“登錄成功"&#xff0c;提供一個Ok按鈕&#xff0c;用戶點擊OK后&#xff0c;關閉登錄界面&#xff0c;跳轉到其他界面 如果賬號和密碼不匹配&…

Java基礎-注解

注解 注解是用來干什么的它有什么作用注解的常見分類內置注解Override注解定義 Deprecated注解定義 SuppressWarnings注解定義 元注解Target注解定義ElementType Retention&&RetentionTarget注解定義RetentionPolicy Documented注解定義 Inherited注解定義用法 Repeata…

低代碼開發:推動互聯網企業數字化轉型的關鍵因素

聯網行業作為我國數字經濟發展的核心驅動力&#xff0c;在推動國家數字化轉型中扮演著至關重要的角色。與其他傳統行業相比&#xff0c;互聯網企業面臨更加緊迫的數字化轉型需求&#xff0c;因為它們需要不斷適應快速變化的市場環境和技術趨勢。 然而&#xff0c;由于互聯網企業…

深入理解APDU協議與Java開發

1. 什么是APDU&#xff1f; APDU&#xff0c;即應用協議數據單元&#xff08;Application Protocol Data Unit&#xff09;&#xff0c;是一種在智能卡與卡片讀卡器之間進行通信的協議。APDU定義了在交互中傳輸的數據格式和規則&#xff0c;允許讀卡器發送指令并接收響應。 2…

MFC 皮膚庫配置

1.創建MFC 對話框 2.添加皮膚資源 添加資源 添加頭文件 關閉SDL檢測 添加靜態庫文件 修改字符集 添加頭文件 將皮膚中的ssk文件加載到初始化實例中 > 運行即可

springboot 的 websocket 里面使用 @Autowired 注入 service 或 bean 時,報空指針異常

直接上解決方案&#xff1a; 在你的WebSocketServer服務器中 public static MessageService messageService; //要注入的類// 注入的時候&#xff0c;給類的 service 注入Autowiredpublic void setChatService(MessageService messageService) {WebSocketServer.messageSer…

【寸鐵的刷題筆記】樹、dfs、bfs、回溯、遞歸(一)

【寸鐵的刷題筆記】樹、dfs、bfs、回溯、遞歸(一) 大家好 我是寸鐵&#x1f44a; 總結了一篇刷題關于樹、dfs、bfs、回溯、遞歸的文章? 喜歡的小伙伴可以點點關注 &#x1f49d; 105. 從前序與中序遍歷序列構造二叉樹 模擬分析圖 代碼實現 /*** Definition for a binary tre…

HarmonyOS—添加/刪除Module

Module是應用/服務的基本功能單元&#xff0c;包含了源代碼、資源文件、第三方庫及應用/服務配置文件&#xff0c;每一個Module都可以獨立進行編譯和運行。一個HarmonyOS應用/服務通常會包含一個或多個Module&#xff0c;因此&#xff0c;可以在工程中創建多個Module&#xff0…