主頁 > 後端開發 > HashMap 是如何作業的?圖文詳解,一起來看看!

HashMap 是如何作業的?圖文詳解,一起來看看!

2020-09-17 08:57:49 後端開發

1 HashMap在JAVA中的怎么作業的?

基于Hash的原理,

2 什么是哈希?

最簡單形式的 hash,是一種在對任何變數/物件的屬性應用任何公式/演算法后, 為其分配唯一代碼的方法,

一個真正的hash方法必須遵循下面的原則:

哈希函式每次在相同或相等的物件上應用哈希函式時, 應每次回傳相同的哈希碼,換句話說, 兩個相等的物件必須一致地生成相同的哈希碼,

Java 中所有的物件都有 Hash 方法,Java中的所有物件都繼承 Object 類中定義的 hashCode() 函式的默認實作,此函式通常通過將物件的內部地址轉換為整數來生成哈希碼,從而為所有不同的物件生成不同的哈希碼,

3 HashMap 中的 Node 類

Map的定義是:將鍵映射到值的物件,

因此,HashMap 中必須有一些機制來存盤這個鍵值對,答案是肯定的,HashMap 有一個內部類 Node,如下所示:

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash; // 記錄hash值, 以便重hash時不需要再重新計算
    final K key;
    V value;
    Node<K,V> next;

    ...// 其余的代碼
}

當然,Node 類具有存盤為屬性的鍵和值的映射,

key 已被標記為 final,另外還有兩個欄位:next 和 hash,

在下面中, 我們將會理解這些屬性的必須性,

4 鍵值對在 HashMap 中是如何存盤的

鍵值對在 HashMap 中是以 Node 內部類的陣列存放的, 如下所示:

transient Node<K,V>[] table;

哈希碼計算出來之后, 會轉換成該陣列的下標, 在該下標中存盤對應哈希碼的鍵值對, 在此先不詳細講解hash碰撞的情況,

該陣列的長度始終是 2 的次冪, 通過以下的函式實作該程序

static final int tableSizeFor(int cap) {
    int n = cap - 1;// 如果不做該操作, 則如傳入的 cap 是 2 的整數冪, 則回傳值是預想的 2 倍
    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;
}

其原理是將傳入引數 (cap) 的低二進制全部變為 1, 最后加 1 即可獲得對應的大于 cap 的 2 的次冪作為陣列長度,

為什么要使用 2 的次冪作為陣列的容量呢?

在此有涉及到 HashMap 的 hash 函式及陣列下標的計算, 鍵(key)所計算出來的哈希碼有可能是大于陣列的容量的, 那怎么辦?

可以通過簡單的求余運算來獲得, 但此方法效率太低,HashMap 中通過以下的方法保證 hash 的值計算后都小于陣列的容量,

(n - 1) & hash

這也正好解釋了為什么需要 2 的次冪作為陣列的容量,由于 n 是 2 的次冪, 因此, n - 1 類似于一個低位掩碼,

通過與操作, 高位的hash值全部歸零,保證低位才有效, 從而保證獲得的值都小于 n,同時, 在下一次 resize() 操作時, 重新計算每個 Node 的陣列下標將會因此變得很簡單, 具體的后文講解,

以默認的初始值 16 為例:

    01010011 00100101 01010100 00100101
&   00000000 00000000 00000000 00001111
----------------------------------
    00000000 00000000 00000000 00000101    //高位全部歸零,只保留末四位
    // 保證了計算出的值小于陣列的長度 n

但是, 使用了該功能之后, 由于只取了低位, 因此 hash 碰撞會也會相應的變得很嚴重,這時候就需要使用 「擾動函式」

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

該函式通過將哈希碼的高 16 位的右移后與原哈希碼進行異或而得到, 以以上的例子為例

異或

此方法保證了高16位不變, 低16位根據異或后的結果改變,計算后的陣列下標將會從原先的 5 變為 0,

使用了 「擾動函式」 之后, hash 碰撞的概率將會下降,有人專門做過類似的測驗, 雖然使用該 「擾動函式」 并沒有獲得最大概率的避免 hash 碰撞, 但考慮其計算性能和碰撞的概率, JDK 中使用了該方法, 且只 hash 一次,

5 哈希碰撞及其處理

在理想的情況下, 哈希函式將每一個 key 都映射到一個唯一的 bucket, 然而, 這是不可能的,哪怕是設計在良好的哈希函式, 也會產生哈希沖突,

前人研究了很多哈希沖突的解決方法, 在維基百科中, 總結出了四大類

哈希碰撞解決方法

在 Java 的 HashMap 中, 采用了第一種 Separate chaining 方法(大多數翻譯為拉鏈法)+鏈表和紅黑樹來解決沖突,

JDK8中HashMap結果

HashMap 中, 哈希碰撞之后會通過 Node 類內部的成員變數 Node<K,V> next; 來形成一個鏈表(節點小于8)或紅黑樹(節點大于8, 在小于6時會從新轉換為鏈表), 從而達到解決沖突的目的,

static final int TREEIFY_THRESHOLD = 8;

static final int UNTREEIFY_THRESHOLD = 6;

6 HashMap 的初始化

public HashMap();
public HashMap(int initialCapacity);
public HashMap(Map<? extends K, ? extends V> m);
public HashMap(int initialCapacity, float loadFactor);

HashMap 中有四個建構式, 大多是初始化容量和負載因子的操作,以 public HashMap(int initialCapacity, float loadFactor) 為例

public HashMap(int initialCapacity, float loadFactor) {
    // 初始化的容量不能小于0
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    // 初始化容量不大于最大容量
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // 負載因子不能小于 0
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

通過該函式進行了容量和負載因子的初始化,如果是呼叫的其他的建構式, 則相應的負載因子和容量會使用默認值(默認負載因子=0.75, 默認容量=16),

在此時, 還沒有進行存盤容器 table 的初始化, 該初始化要延遲到第一次使用時進行,HashMap 面試 21 問!面試的推薦你看下,關注公眾號Java技術堆疊回復面試獲取更多面試資料,

7 HashMap 中哈希表的初始化或動態擴容

所謂的哈希表, 指的就是下面這個型別為內部類Node的 table 變數,

transient Node<K,V>[] table;

作為陣列, 其在初始化時就需要指定長度,在實際使用程序中, 我們存盤的數量可能會大于該長度,因此 HashMap 中定義了一個閾值引數(threshold), 在存盤的容量達到指定的閾值時, 需要進行擴容,

我個人認為初始化也是動態擴容的一種, 只不過其擴容是容量從 0 擴展到建構式中的數值(默認16),而且不需要進行元素的重hash.

7.1 擴容發生的條件

初始化的話只要數值為慷訓者陣列長度為 0 就會進行,而擴容是在元素的數量大于閾值(threshold)時就會觸發,

threshold = loadFactor * capacity

比如 HashMap 中默認的 loadFactor=0.75, capacity=16, 則

threshold = loadFactor * capacity = 0.75 * 16 = 12

那么在元素數量大于 12 時, 就會進行擴容,擴容后的 capacity 和 threshold 也會隨之而改變,

負載因子影響觸發的閾值, 因此, 它的值較小的時候, HashMap 中的 hash 碰撞就很少, 此時存取的性能都很高, 對應的缺點是需要較多的記憶體;而它的值較大時, HashMap 中的 hash 碰撞就很多, 此時存取的性能相對較低, 對應優點是需要較少的記憶體;不建議更改該默認值, 如果要更改, 建議進行相應的測驗之后確定,

7.2 再談容量為2的整數次冪和陣列索引計算

前面說過了陣列的容量為 2 的整次冪, 同時, 陣列的下標通過下面的代碼進行計算

index = (table.length - 1) & hash

該方法除了可以很快的計算出陣列的索引之外, 在擴容之后, 進行重 hash 時也會很巧妙的就可以算出新的 hash 值,由于陣列擴容之后, 容量是現在的 2 倍, 擴容之后 n-1 的有效位會比原來多一位, 而多的這一位與原容量二進制在同一個位置,示例

擴容前后

這樣就可以很快的計算出新的索引啦

7.3 步驟

  1. 先判斷是初始化還是擴容, 兩者在計算 newCap和newThr 時會不一樣

  2. 計算擴容后的容量,臨界值,

  3. 將hashMap的臨界值修改為擴容后的臨界值

  4. 根據擴容后的容量新建陣列,然后將hashMap的table的參考指向新陣列,

  5. 將舊陣列的元素復制到table中,在該程序中, 涉及到幾種情況, 需要分開進行處理(只存有一個元素, 一般鏈表, 紅黑樹)

具體的看代碼吧

final Node<K, V>[] resize() {
        //新建oldTab陣列保存擴容前的陣列table
        Node<K, V>[] oldTab = table;
        //獲取原來陣列的長度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //原來陣列擴容的臨界值
        int oldThr = threshold;
        int newCap, newThr = 0;
        //如果擴容前的容量 > 0
        if (oldCap > 0) {
            //如果原來的陣列長度大于最大值(2^30)
            if (oldCap >= MAXIMUM_CAPACITY) {
                //擴容臨界值提高到正無窮
                threshold = Integer.MAX_VALUE;
                //無法進行擴容,回傳原來的陣列
                return oldTab;
                //如果現在容量的兩倍小于MAXIMUM_CAPACITY且現在的容量大于DEFAULT_INITIAL_CAPACITY
            } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
                //臨界值變為原來的2倍
                newThr = oldThr << 1;
        } else if (oldThr > 0) //如果舊容量 <= 0,而且舊臨界值 > 0
            //陣列的新容量設定為老陣列擴容的臨界值
            newCap = oldThr;
        else { //如果舊容量 <= 0,且舊臨界值 <= 0,新容量擴充為默認初始化容量,新臨界值為DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY
            newCap = DEFAULT_INITIAL_CAPACITY;//新陣列初始容量設定為默認值
            newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//計算默認容量下的閾值
        }
        // 計算新的resize上限
        if (newThr == 0) {//在當上面的條件判斷中,只有是初始化時(oldCap=0, oldThr > 0)時,newThr == 0
            //ft為臨時臨界值,下面會確定這個臨界值是否合法,如果合法,那就是真正的臨界值
            float ft = (float) newCap * loadFactor;
            //當新容量< MAXIMUM_CAPACITY且ft < (float)MAXIMUM_CAPACITY,新的臨界值為ft,否則為Integer.MAX_VALUE
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
                    (int) ft : Integer.MAX_VALUE);
        }
        //將擴容后hashMap的臨界值設定為newThr
        threshold = newThr;
        //創建新的table,初始化容量為newCap
        @SuppressWarnings({"rawtypes", "unchecked"})
        Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
        //修改hashMap的table為新建的newTab
        table = newTab;
        //如果舊table不為空,將舊table中的元素復制到新的table中
        if (oldTab != null) {
            //遍歷舊哈希表的每個桶,將舊哈希表中的桶復制到新的哈希表中
            for (int j = 0; j < oldCap; ++j) {
                Node<K, V> e;
                //如果舊桶不為null,使用e記錄舊桶
                if ((e = oldTab[j]) != null) {
                    //將舊桶置為null
                    oldTab[j] = null;
                    //如果舊桶中只有一個node
                    if (e.next == null)
                        //將e也就是oldTab[j]放入newTab中e.hash & (newCap - 1)的位置
                        newTab[e.hash & (newCap - 1)] = e;
                        //如果舊桶中的結構為紅黑樹
                    else if (e instanceof TreeNode)
                        //將樹中的node分離
                        ((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
                    else {  //如果舊桶中的結構為鏈表,鏈表重排,jdk1.8做的一系列優化
                        Node<K, V> loHead = null, loTail = null;
                        Node<K, V> hiHead = null, hiTail = null;
                        Node<K, V> next;
                        //遍歷整個鏈表中的節點
                        do {
                            next = e.next;
                            // 原索引
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            } else {// 原索引+oldCap
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        // 原索引放到bucket里
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        // 原索引+oldCap放到bucket里
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
}

7.4 注意事項

雖然 HashMap 設計的非常優秀, 但是應該盡可能少的避免 resize(), 該程序會很耗費時間,

同時, 由于 hashmap 不能自動的縮小容量,因此, 如果你的 hashmap 容量很大, 但執行了很多 remove 操作時, 容量并不會減少,如果你覺得需要減少容量, 請重新創建一個 hashmap,

8 HashMap.put() 函式內部是如何作業的?

在使用多次 HashMap 之后, 大體也能說出其添加元素的原理:計算每一個key的哈希值, 通過一定的計算之后算出其在哈希表中的位置,將鍵值對放入該位置,如果有哈希碰撞則進行哈希碰撞處理,

而其作業時的原理如下(圖是我很早之前保存的, 忘了出處了)

原始碼如下:

/* @param hash         指定引數key的哈希值
 * @param key          指定引數key
 * @param value        指定引數value
 * @param onlyIfAbsent 如果為true,即使指定引數key在map中已經存在,也不會替換value
 * @param evict        如果為false,陣列table在創建模式中
 * @return 如果value被替換,則回傳舊的value,否則回傳null,當然,可能key對應的value就是null,
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K, V>[] tab;
    Node<K, V> p;
    int n, i;
    //如果哈希表為空,呼叫resize()創建一個哈希表,并用變數n記錄哈希表長度
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    /**
     * 如果指定引數hash在表中沒有對應的桶,即為沒有碰撞
     * Hash函式,(n - 1) & hash 計算key將被放置的槽位
     * (n - 1) & hash 本質上是hash % n,位運算更快
     */
    if ((p = tab[i = (n - 1) & hash]) == null)
        //直接將鍵值對插入到map中即可
        tab[i] = newNode(hash, key, value, null);
    else {// 桶中已經存在元素
        Node<K, V> e;
        K k;
        // 比較桶中第一個元素(陣列中的結點)的hash值相等,key相等
        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
            // 將第一個元素賦值給e,用e來記錄
            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);
                    // 檢查鏈表長度是否達到閾值,達到將該槽位節點組織形式轉為紅黑樹
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 鏈表節點的<key, value>與put操作<key, value>相同時,不做重復操作,跳出回圈
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 找到或新建一個key和hashCode與插入元素相等的鍵值對,進行put操作
        if (e != null) { // existing mapping for key
            // 記錄e的value
            V oldValue = https://www.cnblogs.com/javastack/p/e.value;
            /**
             * onlyIfAbsent為false或舊值為null時,允許替換舊值
             * 否則無需替換
             */
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            // 訪問后回呼
            afterNodeAccess(e);
            // 回傳舊值
            return oldValue;
        }
    }
    // 更新結構化修改資訊
    ++modCount;
    // 鍵值對數目超過閾值時,進行rehash
    if (++size > threshold)
        resize();
    // 插入后回呼
    afterNodeInsertion(evict);
    return null;
}

在此程序中, 會涉及到哈希碰撞的解決,

9 HashMap.get() 方法內部是如何作業的?

/**
 * 回傳指定的key映射的value,如果value為null,則回傳null
 * get可以分為三個步驟:
 * 1.通過hash(Object key)方法計算key的哈希值hash,
 * 2.通過getNode( int hash, Object key)方法獲取node,
 * 3.如果node為null,回傳null,否則回傳node.value,
 *
 * @see #put(Object, Object)
 */
public V get(Object key) {
    Node<K, V> e;
    //根據key及其hash值查詢node節點,如果存在,則回傳該節點的value值
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

其最終是呼叫了 getNode 函式,其邏輯如下

getNode 作業邏輯

原始碼如下:

 /**
 * @param hash 指定引數key的哈希值
 * @param key  指定引數key
 * @return 回傳node,如果沒有則回傳null
 */
final Node<K, V> getNode(int hash, Object key) {
    Node<K, V>[] tab;
    Node<K, V> first, e;
    int n;
    K k;
    //如果哈希表不為空,而且key對應的桶上不為空
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
        //如果桶中的第一個節點就和指定引數hash和key匹配上了
        if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
            //回傳桶中的第一個節點
            return first;
        //如果桶中的第一個節點沒有匹配上,而且有后續節點
        if ((e = first.next) != null) {
            //如果當前的桶采用紅黑樹,則呼叫紅黑樹的get方法去獲取節點
            if (first instanceof TreeNode)
                return ((TreeNode<K, V>) first).getTreeNode(hash, key);
            //如果當前的桶不采用紅黑樹,即桶中節點結構為鏈式結構
            do {
                //遍歷鏈表,直到key匹配
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    //如果哈希表為空,或者沒有找到節點,回傳null
    return null;
}

作者:阿進的寫字臺
出處:https://www.cnblogs.com/homejim/
本文著作權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利,

近期熱文推薦:

1.終于靠開源專案弄到 IntelliJ IDEA 激活碼了,真香!

2.我用 Java 8 寫了一段邏輯,同事直呼看不懂,你試試看,,

3.吊打 Tomcat ,Undertow 性能很炸!!

4.國人開源了一款超好用的 Redis 客戶端,真香!!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/64339.html

標籤:Java

上一篇:JSON資料處理框架Jackson精解第一篇-序列化與反序列化核心用法

下一篇:Java 15 正式發布, 14 個新特性,重繪你的認知!!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more