主頁 > 後端開發 > HashMap原理底層剖析

HashMap原理底層剖析

2021-05-04 08:48:23 後端開發

注意以下文章可能有描述和理解上的錯誤,如果出現錯誤請到評論區指出,我會第一時間修改問題,也希望文章能解決你的疑惑,

HashMap結構圖

HashMap底層資料結構:Entry陣列+鏈表+紅黑樹(JDK1.8版本) Entry+鏈表(JDK1.7版本)
在這里插入圖片描述

這里寫目錄標題

    • HashMap結構圖
    • 代碼分析
      • 常見的引數及意義
      • 原始碼解釋
        • 構造方法
        • size函式
        • isEmpty函式
        • get具體程序函式
        • containsKey函式
        • put函式
        • resize函式
        • remove函式
        • clear函式
        • containsValue函式
        • keySet函式
        • values函式
        • entrySet函式
      • 面試常見的問題

代碼分析

常見的引數及意義

	//默認的Hash表的長度
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
	//Hash表的最大長度
    static final int MAXIMUM_CAPACITY = 1 << 30;
	//默認加載因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
	//當鏈表的長度為8的時候轉化為紅黑樹
    static final int TREEIFY_THRESHOLD = 8;
	//桶中元素個數小于6的時候紅黑樹轉換為鏈表
    static final int UNTREEIFY_THRESHOLD = 6;
	//只有當陣列的長度大于等于64并且鏈表個數大于8才會轉換為紅黑樹
    static final int MIN_TREEIFY_CAPACITY = 64;
    //Hash表
	transient Node<K,V>[] table;
	//遍歷的時候使用回傳一個K-V集合
    transient Set<Map.Entry<K,V>> entrySet;
	//表中K-V的個數
    transient int size;
	//對集合的修改次數,主要是后面出現的集合校驗
    transient int modCount;
	//閾值當size大于threshold時就會進行resize
    int threshold;
	//加載因子
    final float loadFactor;

原始碼解釋

構造方法

//傳入初始化容量,和指定的加載因子
    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;
        //回傳的是2的整數次冪
        this.threshold = tableSizeFor(initialCapacity);
    }
    
    //指定HashMap的容量
    public HashMap(int initialCapacity) {
            //呼叫如上的雙參建構式
            this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
    //無參建構式
    public HashMap() {
            //初始化加載因子為默認的加載因子
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
     }
     
     //構造一個映射關系與指定 Map 相同的新 HashMap,
     public HashMap(Map<? extends K, ? extends V> m) {
             //初始化加載因子為默認的加載因子
             this.loadFactor = DEFAULT_LOAD_FACTOR;
             //構造的程序函式
             putMapEntries(m, false);
         }
     
     final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
             //獲取m集合中元素個數
             int s = m.size();
             //如果m集合元素個數是0個那么下面這些操作也就沒有必要了
             if (s > 0) {
                 if (table == null) { //表示的拷貝建構式呼叫putMapEntries函式,或者是構造了HashMap但是還沒有存放元素
                     //計算的值存在小數所以+1.0F向上取整
                     float ft = ((float)s / loadFactor) + 1.0F;
                     //將ft強制轉換為整形
                     int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                              (int)ft : MAXIMUM_CAPACITY);
                     //如果計算出來的值大于當前HashMap的閾值更新新的閾值為2次方
                     if (t > threshold)
                         threshold = tableSizeFor(t);
                 }
                 else if (s > threshold)//如果Map集合元素大于當前集合HashMap的閾值則進行擴容
                     resize();
                 //將Map集合中元素存放到當前集合中
                 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                     K key = e.getKey();
                     V value = e.getValue();
                     putVal(hash(key), key, value, false, evict);
                 }
             }
         }

size函式

 //回傳key-val的數量
     public int size() {
             return size;
         }

isEmpty函式

   //當前的集合是否為null
     public boolean isEmpty() {
             return size == 0;
         }

get具體程序函式

//根據key獲取對應的val
     public V get(Object key) {
             Node<K,V> e;
             //通過hash值,key找到目標節點再回傳對應的val
             return (e = getNode(hash(key), key)) == null ? null : e.value;
         }

//獲取key對應的節點  
     final Node<K,V> getNode(int hash, Object key) {
             Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
             //如果集合為空和對應的下標陣列中的值為空直接回傳null
             //first = tab[(n - 1) & hash]陣列的長度是2n次方減1后對應位全部變為1,這樣為與操作永遠都會在陣列下標范圍內不會越界
             if ((tab = table) != null && (n = tab.length) > 0 &&
                 (first = tab[(n - 1) & hash]) != null) {
                 if (first.hash == hash && // 如果第一個節點hash與對應hash相等,并且key也相等則回傳當前節點
                     ((k = first.key) == key || (key != null && key.equals(k))))
                     return first;
                 //第一個節點的下一個節點不為null
                 if ((e = first.next) != null) {
                     //判斷節點是否為樹形
                     if (first instanceof TreeNode)
                         //在樹形結構中查找節點并回傳
                         return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                     do {//通過do...while結構遍歷找對應key的節點
                         if (e.hash == hash &&
                             ((k = e.key) == key || (key != null && key.equals(k))))
                             //找到節點并回傳
                             return e;
                     } while ((e = e.next) != null);
                 }
             }
             //未找到對應的節點
             return null;
         }

containsKey函式

	//查看是否包含指定key    
      public boolean containsKey(Object key) {
             //通過getNode回傳是否為null判斷是否存在key
             return getNode(hash(key), key) != null;
         }

put函式

在此之前先看一下put的程序
在這里插入圖片描述

//呼叫putVal向當前集合中存放元素并回傳對應的val
      public V put(K key, V value) {
              return putVal(hash(key), key, value, false, true);
          }
          
      //存放對應的key-val
      final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                         boolean evict) {
              Node<K,V>[] tab; Node<K,V> p; int n, i;
              //如果當前集合為null則將集合擴容并且將新的存放結構賦值給tab
              if ((tab = table) == null || (n = tab.length) == 0)
                  n = (tab = resize()).length;
              //找到key存放的鏈表,如果為空直接將當前節點存放鏈表在第一個位置
              if ((p = tab[i = (n - 1) & hash]) == null)
                  tab[i] = newNode(hash, key, value, null);
              else { //當前為鏈表不為null
                  Node<K,V> e; K k;
                  //表示當前鏈表第一個位置key已經存在,將當前節點賦值給e
                  if (p.hash == hash &&
                      ((k = p.key) == key || (key != null && key.equals(k))))
                      e = p;
                  //查看當前的節點是否屬于樹形結構如果是則在TreeNode中查找并將賦值給e
                  else if (p instanceof TreeNode)
                      e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                  else {
                      for (int binCount = 0; ; ++binCount) {
                          //找到當前存放位置節點的最后一個節點的next并將當前要插入的節點插入
                          if ((e = p.next) == null) {
                              p.next = newNode(hash, key, value, null);
                              if (binCount >= TREEIFY_THRESHOLD - 1) // 鏈表的長度為8的時候轉化為紅黑樹減一是因為元素從0開始
                                  treeifyBin(tab, hash);
                              //跳出死回圈
                              break;
                          }
                          //表示的是當前鏈表已經存在當前要插入的key,HashMap不存在重復的key
                          if (e.hash == hash &&
                              ((k = e.key) == key || (key != null && key.equals(k))))
                              break;
                          //將節點后移
                          p = e;
                      }
                  }
                  if (e != null) { // 當前節點不為null將e.val存放在oldValue
                      V oldValue = e.value;
                      if (!onlyIfAbsent || oldValue == null)//不管oldValue是否為null都會發生value賦值給e.value
                          //當出現重復的key之后上面會將節點保存給e并未修改新的val值,在此更新
                          e.value = value;
                      //將結點向后調整到最后面
                      afterNodeAccess(e);
                      //如果為null回傳null,不為null回傳對應的val
                      return oldValue;
                  }
              }
              //++modCount對其集合操作的次數+1
              ++modCount;
              if (++size > threshold)//如果在放入元素以后大于閾值則進行2倍擴容
                  resize();
              afterNodeInsertion(evict);
              return null;
          }
      

resize函式

 //將集合擴容
          final Node<K,V>[] resize() {
                  Node<K,V>[] oldTab = table;
                  //舊表的容量
                  int oldCap = (oldTab == null) ? 0 : oldTab.length;
                  //之前的閾值
                  int oldThr = threshold;
                  int newCap, newThr = 0;
                  //這里也可以說集合不為空
                  if (oldCap > 0) {
                      if (oldCap >= MAXIMUM_CAPACITY) {//如果集合現在陣列的長度大于等于最大容量
                          threshold = Integer.MAX_VALUE;//將整型最大的值賦值給threshold
                          return oldTab;
                      }
                      //當前集合陣列長度擴大二倍賦值給newCap小于MAXIMUM_CAPACITY
                      //并且集合的容量大于等于默認容量將當前閾值擴大二倍賦值給新的閾值 
                      else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                               oldCap >= DEFAULT_INITIAL_CAPACITY)
                          newThr = oldThr << 1; // double threshold
                  }
                  //若沒有經歷過初始化,通過建構式指定了initialCapcity,將當前容量設定為大于它最小的2的n次方
                  else if (oldThr > 0) 
                      newCap = oldThr;
                  else {               // 初始的時候長度和閾值都使用默認值
                      newCap = DEFAULT_INITIAL_CAPACITY;
                      newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
                  }
                  //重新計算threshold
                  if (newThr == 0) {
                      float ft = (float)newCap * loadFactor;
                      newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                                (int)ft : Integer.MAX_VALUE);
                  }
                  //更新當前集合閾值
                  threshold = newThr;
                  //從這里開始便是將oldTab資料重新hash放入擴容后的newTab
                  @SuppressWarnings({"rawtypes","unchecked"})
                      Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
                  //將table指向的oldTab指向newTab
                  table = newTab;
                  if (oldTab != null) {
                      //遍歷哈希表
                      for (int j = 0; j < oldCap; ++j) {
                          Node<K,V> e;
                          //當前鏈表是否為null、并且將就鏈表賦值給e
                          if ((e = oldTab[j]) != null) {
                              oldTab[j] = null;//將原來位置的鏈表置為null方便垃圾回收
                              if (e.next == null)//鏈表的長度為1直接將鏈表中的一個節點重新hash存放到相應的位置
                                  newTab[e.hash & (newCap - 1)] = e;
                              else if (e instanceof TreeNode) //表示節點型別為樹形結構
                                  ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                              else { //鏈表是非樹形結構,并且節點數量是大于1
                                  //將鏈表拆分為兩個子鏈表
                                  Node<K,V> loHead = null, loTail = null;
                                  Node<K,V> hiHead = null, hiTail = null;
                                  Node<K,V> next;
                                  do {  //通過do...while遍歷鏈表
                                      next = e.next;
                                      if ((e.hash & oldCap) == 0) {
                                          if (loTail == null) //設定頭節點
                                              loHead = e;
                                          else            //設定尾結點
                                              loTail.next = e;
                                          loTail = e;//將尾結點變為最后一個節點
                                      }
                                      else {
                                          if (hiTail == null)//同上都是設定頭節點下面也一樣是設定尾結點
                                              hiHead = e;
                                          else
                                              hiTail.next = e;
                                          hiTail = e;
                                      }
                                  } while ((e = next) != null);
                                  if (loTail != null) {//在新表的j位置存放鏈表
                                      loTail.next = null;
                                      newTab[j] = loHead;
                                  }
                                  if (hiTail != null) {//在新表的j+oldCap位置存放鏈表
                                      hiTail.next = null;
                                      newTab[j + oldCap] = hiHead;
                                  }
                              }
                          }
                      }
                  }
                  return newTab;
              }

remove函式

 	// 移除指向key回傳對應的val          
    public V remove(Object key) {
            Node<K,V> e;
            //回傳如果為慷訓傳null否則回傳e.val
            return (e = removeNode(hash(key), key, null, false, true)) == null ?
                null : e.value;
        }
        
    final Node<K,V> removeNode(int hash, Object key, Object value,
                                   boolean matchValue, boolean movable) {
            Node<K,V>[] tab; Node<K,V> p; int n, index;
            //常規的判斷表不為null,key有對應的存盤位置
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (p = tab[index = (n - 1) & hash]) != null) {
                Node<K,V> node = null, e; K k; V v;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    //表示的是key存盤在當前鏈表的第一個位置
                    node = p;
                else if ((e = p.next) != null) {//表示的是鏈表的長度大于1
                    if (p instanceof TreeNode)//判斷是否是樹的實列
                        //回傳對應key在紅黑樹存盤的位置
                        node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                    else {//當前結構為鏈表
                        do {//遍歷鏈表
                            if (e.hash == hash &&
                                ((k = e.key) == key ||
                                 (key != null && key.equals(k)))) {//找到對應的節點保存并跳出回圈
                                node = e;
                                break;
                            }
                            //將節點后移
                            p = e;
                        } while ((e = e.next) != null);
                    }
                }
                //表示要洗掉的key存在并且找到
                if (node != null && (!matchValue || (v = node.value) == value ||
                                     (value != null && value.equals(v)))) {
                    if (node instanceof TreeNode)//如果是樹形在樹型結構中移除當前節點
                        ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                    else if (node == p)//表示的鏈表中的第一個節點
                        tab[index] = node.next;
                    else
                        p.next = node.next;//移除節點
                    ++modCount;//操作+1
                    --size;//長度-1
                    afterNodeRemoval(node);
                    //回傳節點
                    return node;
                }
            }
            return null;
        }

clear函式

//清除集合中的所有key-value      
        public void clear() {
                Node<K,V>[] tab;
                //集合操作+1
                modCount++;
                if ((tab = table) != null && size > 0) {//表不為null才進行遍歷
                    size = 0;
                    for (int i = 0; i < tab.length; ++i)//遍歷集合所有元素都置為null,方便垃圾回收
                        tab[i] = null;
                }
            }

containsValue函式

 	//查看集合是否包含指定value
    public boolean containsValue(Object value) {
            Node<K,V>[] tab; V v;
            if ((tab = table) != null && size > 0) {//表不為null
                for (int i = 0; i < tab.length; ++i) {//遍歷陣列
                    for (Node<K,V> e = tab[i]; e != null; e = e.next) {//遍歷鏈表
                        if ((v = e.value) == value ||
                            (value != null && value.equals(v)))
                            //存在指定的value直接回傳true
                            return true;
                    }
                }
            }
            //集合中不存在指定value回傳false
            return false;
        }

keySet函式

	//回傳key的所有集合set
    public Set<K> keySet() {
            Set<K> ks = keySet;
            if (ks == null) {
                ks = new KeySet();
                keySet = ks;
            }
            return ks;
        }      

values函式

	//回傳所有的value集合    
    public Collection<V> values() {
            Collection<V> vs = values;
            if (vs == null) {
                vs = new Values();
                values = vs;
            }
            return vs;
        }

entrySet函式

   // 回傳所有的key-value集合
    public Set<Map.Entry<K,V>> entrySet() {
          Set<Map.Entry<K,V>> es;
          return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
      }

面試常見的問題

  1. 為什么HashMap默認的長度為2的整數次冪?

    就是因為獲取索引h&(length-1)可以保證散列的均勻,避免不必要的hash沖突,

  2. 為什么加載因子是0.75?大了會怎么樣?小了會怎么樣?

    首先加載因子是表示hash表的填滿程度,當為0.75的時候是在對提高空間利用率和減少查詢成本的折中,當大于0.75的時候填滿的元素越多,空間利用率越高,但是沖突的概率變大;當小于0.75的時候填滿的元素越少,空間利用率越低,但是沖突的概率變小,

  3. 什么是哈希沖突?如何解決?

    哈希沖突是指hash出來的地址被其他元素所占用;
    解決的方法
    1.鏈地址法
    解決的思路就是當出現沖突的時候將沖突的元素加入當前的鏈表之中
    在這里插入圖片描述
    2.開放地址法
    開放地址法也稱之為再散列,
    思路:如果映射的地址被占用了,在哈希函式值的基礎上加上指定數值,這樣就可以把沖突的地址給錯開,然后重新開辟新的地址用來存盤,根據增量值的不同,分為線性探測再散列和二次探測再散列
    在這里插入圖片描述
    3.再哈希法
    這種方法就是構造多個不同的哈希函式,當哈希地址Hi=RH1(Key)發生沖突時,再計算Hi=RH2(Key)…直到哈希不沖突,這樣的方法增加了計算的時間,
    4.建立公共溢區
    就是哈希表分成了兩個表:一個是基礎表,另外一個則是溢位表,凡是與基礎表發生沖突的資料都會被添加到溢位表,

  4. 什么是擾動函式?怎么設計的?為什么這個設計?

    擾動函式是hash函式拿到k的hashcode值,這個值是一個32位的int,讓高16位與低16位進行異或,
    理論上來說字串的hashCode是一個int型別值,那可以直接作為陣列下標了,且不會出現碰撞,但是這個hashCode的取值范圍是[-2147483648, 2147483647],有將近40億的長度,誰也不能把陣列初始化的這么大,記憶體也是放不下的,
    混合原始哈希碼的高位和低位,以此來加大低位的隨機性,這樣設計在一定的程度上減少了hash碰撞,優化了散列的效果 ,

  5. JDK1.8在對HashMap較1.7有什么優化?

    1.首先是最重要的就是底層的資料結構,1.7的時候底層資料結構是陣列+鏈表;而在1.8的時候變成了陣列+鏈表+紅黑樹
    2.在哈希上1.7擾動四次,1.8做了一次擾動,可以提高效率
    3.1.7在進行resize擴容的時候是重新哈希,1.8的時候采用的是索引位置不變或者就是就哈希表的容量+當前索引,
    4.1.7采用插入方式是頭插法,1.8采用的是尾插法,

  6. 為什么1.8擴容不用重新哈希?
    在這里插入圖片描述

  7. HashMap執行緒安全嗎?為什么不安全?怎么解決不安全?

    首先HashMap是執行緒不安全的,JDK1.7的時候采用頭插法,多執行緒同時插入的時候,A執行緒在插入節點B,B執行緒也在插入,遇到容量不夠開始擴容,重新hash,放置元素,采用頭插法,后遍歷到的B節點放入了頭部,這樣形成了環,JDK1.8采用尾插法,會造成兩種情況兩個執行緒同時插入只有一個成功插入,還有就是可能會造成兩次resize(++size > threshold) ,解決的方案:一、使用HashTable效率比較差,二、使用ConcurrentHashMap比較常用的,三、使用Collections.synchronizedMap() 以上三種執行緒安全,

  8. HashMap內部節點是有序的嗎?

    不是有序的,有序的Map集合有LinkedHashMap、TreeMap

  9. HashMap一般采用什么作為key?

    HashMap一般采用String、Integer 等類作為key、因為這些類底層已經重寫了hashcode、equals方法,用的是final修飾類在多執行緒情況下相對安全,

  10. 為什么重寫equals還要重寫hashcode?

    比如HashMap中不允許存在相同的key,當重寫了equals方法沒有重寫hashcode方法,當兩個物件中的值相同,但是他們hashcode不同會造成比如
    class newInstance1 = new class(1);
    class newInstabce2 = new class(2);
    以上的比較物件的時候hashcode不同,equal方法比較回傳false;但是重寫Hashcode后,可以達到回傳true,

如果看完覺得得到幫助就留下你的一鍵三連,謝謝 注意如果有錯誤的地方評論區提出來我立即更正謝謝大佬的指正,

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

標籤:java

上一篇:2021Q1最受歡迎語言,你get到了嗎?

下一篇:用戶登錄功能的實作

標籤雲
其他(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