国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

由淺入深了解HashMap源碼

這篇具有很好參考價值的文章主要介紹了由淺入深了解HashMap源碼。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

? ? ? ?由經典面試題引入,講解一下HashMap的底層數(shù)據(jù)結構?這個面試題你當然可以只答,HashMap底層的數(shù)據(jù)結構是由(數(shù)組+鏈表+紅黑樹)實現(xiàn)的,但是顯然面試官不太滿意這個答案,畢竟這里有一個坑需要你去填,那就是在回答HashMap的底層數(shù)據(jù)結構時需要考慮JDK的版本,因為在JDK8中相較于之前的版本做了一些改進,不僅僅是增加了紅黑樹的數(shù)據(jù)結構、還包括了鏈表結點的插入由頭插法改成了尾插法,這些都是底層數(shù)據(jù)結構的優(yōu)化問題。

?JDK8中HashMap的數(shù)據(jù)結構

由淺入深了解HashMap源碼

? ? ? ? 從上面數(shù)據(jù)結構原理圖中我們能看出數(shù)組和鏈表是如何組合使用的,數(shù)組不是實際保存數(shù)據(jù)的結構,數(shù)組保存的是Node<K,V>的對象引用地址,實際保存數(shù)據(jù)的是Node<K,V>結點類。數(shù)組中的每個位置只能保存一個Node<K,V>對象,通過鏈表可以在同一位置保存多個數(shù)據(jù),還有鏈表會在一定條件下轉化為紅黑樹。

  • table數(shù)組
// 用于保存 Node<K,V> 類型的數(shù)組
transient Node<K,V>[] table;
  • ?HashMap的默認初始化容量,指的是table數(shù)組大小
 // HashMap的默認初始化容量為16,1位運算左移4位
 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
  • ?HashMap的默認負載因子,用于計算閾值
// HashMap的負載因子用于計算閾值,超過閾值即負載過大需要數(shù)組擴容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • ?HashMap的閾值,當HashMap中的元素個數(shù)超過閾值時,數(shù)組會擴容為原大小的2倍
 // 擴容的閾值(默認閾值 = 默認數(shù)組容量 * 負載因子),默認為12 = 16 * 0.75
 int threshold;
  • ?HashMap的鏈表轉換為紅黑樹的閾值
// 樹化的閾值,不是唯一條件,而是必須條件
static final int TREEIFY_THRESHOLD = 8;
  • Node<K,V>結點類
 // HashMap源碼的靜態(tài)內部類
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;    // 保存key計算出的hash碼
        final K key;       // 保存key的值
        V value;           // 保存value的值
        Node<K,V> next;    // 保存下一個結點的引用地址

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
        ...
 }

?HashMap的構造方法

  • HashMap的無參構造
// 此時只設置了默認的負載因子,即數(shù)組未初始化
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; 
 }
  • HashMap的帶負載因子和數(shù)組容量的構造方法
 // 可設置自定義負載因子和數(shù)組容量
public HashMap(int initialCapacity, float loadFactor) {
     // 數(shù)組容量不能小于0
     if (initialCapacity < 0)
         throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);
     // 數(shù)組容量不能大于MAXIMUM_CAPACITY
     if (initialCapacity > MAXIMUM_CAPACITY)
         initialCapacity = MAXIMUM_CAPACITY;
     // 負載因子不能小于等于0
     if (loadFactor <= 0 || Float.isNaN(loadFactor))
         throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
     this.loadFactor = loadFactor;
     // 用于計算table數(shù)組的最終大小,因為數(shù)組大小必需為2的n次方數(shù)
     this.threshold = tableSizeFor(initialCapacity);
}
  • HashMap的可傳入Map集合數(shù)據(jù)的構造方法
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    // 遍歷Map取出集合的數(shù)據(jù)依次放入HashMap中
    putMapEntries(m, false);
}

?HashMap的put方法

  • ?HashMap的put方法,實際調用了putVal方法
public V put(K key, V value) {
    // 計算key的哈希值,創(chuàng)建Node<K,V>結點放入數(shù)組中
    return putVal(hash(key), key, value, false, true);
}
  • ?HashMap的hash方法返回的哈希值,并不是直接調用Object對象的hashCode()方法返回的那個哈希值,而是經過了異或運算后的哈希值。
// 計算key的哈希值的方法
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • ?異或運算就是兩個二進制數(shù)值按位比較,同位的值相同為0,同位的值不同為1。我們分析一下,先計算出key的哈希值用作第一個數(shù),再把key的哈希值右移16位作為第二個數(shù),然后再把兩個數(shù)值進行異或運算(^)。因為增加了高16位的參與,這樣計算出的哈希值會利用到了key的所有信息參與運算,增加了擾動因素,能降低哈希沖突的概率。還有在后面計算數(shù)組下標時我們會發(fā)現(xiàn),由于是使用與運算(&),在數(shù)組的長度沒有達到2的17次方的情況下,高16位是無法使用到的,所以這也是增加高16位運算的原因。
// 425918570 對應的32位的二進制哈希值 

// 高16位                    低16位
0001 1001 0110 0011 --- 0000 0000 0110 1010     // 原始哈希值
0000 0000 0000 0000 --- 0001 1001 0110 0011     // 原始哈希值右移16位的值,即高16位     
0001 1001 0110 0011 --- 0001 1001 0000 1001     // 異或運算得到的哈希值
  • putVal方法是添加數(shù)據(jù)的核心方法,table數(shù)組會在第一次添加元素時進行初始化,默認初始化容量為16,在添加數(shù)據(jù)時需要通過哈希值計算出數(shù)據(jù)放入的table數(shù)組所在的索引下標位置。添加完元素后需要判斷數(shù)組是否需要擴容,擴容大小是原數(shù)組的兩倍。
 // HashMap添加數(shù)據(jù)的核心方法,
 // onlyIfAbsent = false表示key相等時會覆蓋舊value
 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // table數(shù)組是第一次使用時才進行初始化,懶惰使用
        if ((tab = table) == null || (n = tab.length) == 0)
            // resize()包括了數(shù)組的初始化和擴容
            n = (tab = resize()).length;
        // table數(shù)組當前計算出的下標位置還未保存過元素
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 創(chuàng)建新結點,直接放入計算出的數(shù)組下標位置中
            tab[i] = newNode(hash, key, value, null);
         else {
            Node<K,V> e; K k;
            // 出現(xiàn)哈希沖突,需要判斷是否是相同key,因為不同key也可能有相同的哈希值
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // key相同,賦值給e,走下面更新新值并返回舊值代碼
                e = p;
            // 當前計算的數(shù)組下標保存的是樹結點,即紅黑樹結構
            else if (p instanceof TreeNode)
                // 采用紅黑樹的插入方法
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                // 從數(shù)組下標所在的頭結點開始遍歷鏈表
                for (int binCount = 0; ; ++binCount) {
                     // 找到尾結點,在尾結點后插入新結點,即尾插法
                    if ((e = p.next) == null) {
                        // 先插入結點,再判斷閾值,故鏈表長度大于8時才是樹化必須條件
                        p.next = newNode(hash, key, value, null);
                        // 鏈表的長度大于8時,走樹化方法
                        if (binCount >= TREEIFY_THRESHOLD - 1) 
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 如果當前遍歷到的鏈表結點和需要添加的數(shù)據(jù)key相同,則無需插入,直接退出
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 添加結點數(shù)據(jù)時發(fā)現(xiàn)key已經存在,此時不是插入而是更新
            if (e != null) { 
                V oldValue = e.value;
                 // put相同key的數(shù)據(jù)時會覆蓋舊值
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                // 返回當前key的舊值
                return oldValue;
            }
        }
        ++modCount;
        // HashMap中元素個數(shù)大于閾值,進行擴容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);   
        return null;
}
  • putVal方法中添加Node<K,V>的結點元素時,如何計算元素所在的table數(shù)組下標位置呢?這里就需要用到我們的哈希值了,我們可以利用哈希值對數(shù)組的長度進行取模運算,這樣就可以把取模運算的結果當做數(shù)組的下標位置。但是實際源碼中并不是這樣做的,而是采用了與運算(&),利用哈希值和數(shù)組的最大索引下標進行按位與運算。這兩種方式都可以保證算出的下標都可能取到數(shù)組的所有桶下標,且不會數(shù)組越界,但是與運算效率更高。
// 32位的二進制哈希值

// 高16位                    低16位
0001 1001 0110 0011 --- 0001 1001 0000 1001   // 最終計算出的哈希值
0000 0000 0000 0000 --- 0000 0000 0000 1111   // table數(shù)組的最大索引下標,使用默認大小就是15
0000 0000 0000 0000 --- 0000 0000 0000 1001   // 與運算(&)的結果 9,即下標位置

HashMap的數(shù)組長度為2的n次方數(shù)的原因

  • 面試經常會問HashMap的數(shù)組長度為什么強制使用2的冪次方數(shù)?要回答這個問題,那么我們就需要知道數(shù)組長度在哪個地方用到了,通過源碼我們發(fā)現(xiàn)數(shù)組的長度被用于添加元素時計算數(shù)組的下標位置。數(shù)組下標是通過與運算(&)計算出來的,與運算的特點是(全1為1,有0為0),而2的冪次方數(shù)減一正好保證后面全為1,這樣就可以使與運算計算的結果降低相同值的概率,本質就是減少哈希沖突的概率。
// 數(shù)組下標計算公式
int i = (n - 1) & hash

// 當hash值為10,數(shù)組長度n為9時
// 高16位                    低16位
0000 0000 0000 0000 --- 0000 0000 0000 1010   // 影響&運算的有效位為1位,容易產生哈希沖突
0000 0000 0000 0000 --- 0000 0000 0000 1000   // (n - 1)的值為8
0000 0000 0000 0000 --- 0000 0000 0000 1000   // 計算出的數(shù)組下標為8

// 當hash值為10,數(shù)組長度n為16時
// 高16位                    低16位
0000 0000 0000 0000 --- 0000 0000 0000 1010   // 影響&運算結果的有效位為4位,不易產生哈希沖突
0000 0000 0000 0000 --- 0000 0000 0000 1111   // (n - 1)的值為15
0000 0000 0000 0000 --- 0000 0000 0000 1010   // 計算出的數(shù)組下標為10

  • 數(shù)組擴容后Node<K,V>元素的存放的數(shù)組下標位置變化不大,只有兩種可能,不是在新數(shù)組的原索引值位置,就是在原索引值+原數(shù)組長度的位置。我們知道數(shù)組擴容是在原數(shù)組容量的基礎上乘以2,根據(jù)原理可推斷,在重新計算元素保存的數(shù)組下標位置時,(n - 1)帶來的影響很小,只會增加一個有效位的計算。即擴容前(n - 1) = 15是4個1,擴容后(n - 1) = 31是5個1。
// 數(shù)組下標計算公式
int i = (n - 1) & hash
 
// 當hash值為10,擴容前,數(shù)組長度n為16
// 高16位                    低16位
0000 0000 0000 0000 --- 0000 0000 0000 1010   // &運算結果的有效位為4位
0000 0000 0000 0000 --- 0000 0000 0000 1111   // (n - 1)的值為15
0000 0000 0000 0000 --- 0000 0000 0000 1010   // 計算出的數(shù)組下標為10
 
// 當hash值為10,擴容后,數(shù)組長度n為32
// 高16位                    低16位
0000 0000 0000 0000 --- 0000 0000 0000 1010   // &運算結果的有效位為5位,增加一位有效位計算
0000 0000 0000 0000 --- 0000 0000 0001 1111   // (n - 1)的值為31
0000 0000 0000 0000 --- 0000 0000 0000 1010   // 計算出的數(shù)組下標還是10

HashMap的鏈表樹化的條件

  • ?鏈表什么時候樹化,我們可以查看在添加結點時判斷是否需要樹化的源碼邏輯。
// 樹化的閾值,不是唯一條件,而是必須條件
static final int TREEIFY_THRESHOLD = 8;

 // 當遍歷的結點數(shù)大于等于8時
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
     // 進入是否需要樹化的方法
     treeifyBin(tab, hash);
  • ? 從源碼中我們可以了解到鏈表轉換為紅黑樹需要滿足兩個條件:一是數(shù)組的容量需要大于64,二是鏈表的長度要大于閾值8。當必要條件鏈表的長度要大于8時,才會選擇優(yōu)化縮短鏈表長度,此時不一定就需要直接轉換為紅黑樹,還可以通過擴容數(shù)組的方式來使鏈表長度變短,所以當數(shù)組的容量小于64時,是采取數(shù)組擴容來優(yōu)化鏈表的。
// 將鏈表結點轉換為紅黑樹,如果數(shù)組容量太小則先擴容數(shù)組
final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        // 數(shù)組為空或容量小于64,擴容數(shù)組
        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);
            // 將數(shù)組中的普通結點鏈表替換成樹結點的雙向鏈表
            if ((tab[index] = hd) != null)
                // 構造生成紅黑樹
                hd.treeify(tab);
        }
 }

?JDK8的HashMap為什么在鏈表中使用尾插法代替了頭插法

  • 鏈表結點的頭插法,就是在鏈表的頭部插入數(shù)據(jù),就是每次插入的新結點數(shù)據(jù)都會成為鏈表的頭結點。JDK7的HashMap中是否真的使用了頭插法,我們可以從源碼中求證。
 // 用于添加Entry<K,V>結點的方法,jdk1.7叫Entry
void createEntry(int hash, K key, V value, int bucketIndex) {

    // table數(shù)組保存的頭結點Entry保存到e變量
    Entry<K,V> e = table[bucketIndex];
    
    // 1. 把e元素作為新結點的next結點,即原頭結點作為新結點的下一個結點
    // 2. 新結點作為頭結點保存到table[bucketIndex],即頭插法
    table[bucketIndex] = new Entry<>(hash, key, value, e);

    // HashMap中保存的元素總數(shù)+1
    size++;

}
  • 鏈表結點的尾插法,就是在鏈表的尾部插入數(shù)據(jù),這樣就需要遍歷鏈表找到尾結點,使尾結點的Node<K,V> next結點指向新結點。JDK8的HashMap添加結點使用尾插法的源碼實現(xiàn)就是如此,前面在源碼中已經看到過。
 // 從數(shù)組下標所在的頭結點開始遍歷鏈表
for (int binCount = 0; ; ++binCount) {
      // 找到尾結點,在尾結點后插入新結點,即尾插法
      if ((e = p.next) == null) {
           // 新結點作為尾結點的next結點,并成為新尾結點
           p.next = newNode(hash, key, value, null);
          // 鏈表的長度大于8時,走樹化方法
          if (binCount >= TREEIFY_THRESHOLD - 1) 
              treeifyBin(tab, hash);
          break;
      }
      // 如果當前遍歷到的鏈表結點和需要添加的數(shù)據(jù)key相同,則無需插入,直接退出
      if (e.hash == hash &&
             ((k = e.key) == key || (key != null && key.equals(k))))
           break;
      p = e;
}
  • ?了解了頭插法和尾插法的區(qū)別,那么JDK8中的HashMap為什么拋棄了頭插法而使用尾插法呢?這是因為頭插法在數(shù)組擴容時,鏈表重新在新數(shù)組中生成時會導致鏈表元素倒序,這里比較難理解。因為鏈表的遍歷是從頭結點開始的,而鏈表插入是頭插法,會一直更新頭結點;所以就是最早插入的結點成為尾結點,最后插入的結點成為頭結點,這是頭插法的特點。這里的鏈表元素倒序表面上看起來問題不大,但是在多線程同時操作一個HashMap的情況下容易產生死鏈問題,這才是根本的。

?由淺入深了解HashMap源碼

  • ??JDK7中的HashMap在數(shù)組擴容時為什么會死鏈,當然這是并發(fā)問題,是可能出現(xiàn),不是說一定就會死鏈。我們要結合擴容的源碼進行分析,不一定要非常深入理解,能找到問題所在就行。
// jdk1.7的擴容過程
void transfer(Entry[] newTable, boolean rehash) {
     // 獲取新數(shù)組的容量
     int newCapacity = newTable.length;
     // 遍歷舊數(shù)組,獲取頭結點,單節(jié)點也是鏈表
     for (Entry<K,V> e : table) {
          // 遍歷鏈表
          while(null != e) {
              // 取出當前結點的next結點
             Entry<K,V> next = e.next;
              // 判斷是否需要重新計算hash值
             if (rehash) {
                  e.hash = null == e.key ? 0 : hash(e.key);
             }
             // 因為數(shù)組擴容了,重新計算元素存放的數(shù)組位置
             int i = indexFor(e.hash, newCapacity);

              // 當前添加的結點e作為頭結點,所以它的e.next指向舊的頭結點
             e.next = newTable[i];
             // 最新添加的結點成為新數(shù)組保存的頭結點,每次更新頭結點,即頭插法
             newTable[i] = e;
               
             // 遍歷的寫法,取下一個節(jié)點,
             e = next;    
          }
     }
}
  • ?分析Entry[ ] newTable作為外部傳入的引用變量存在線程安全問題,Entry[ ] table不是方法的局部變量也存在線程安全問題。我們通過分析知道,頭插法會導致鏈表結點元素倒序,即從A > B > C變?yōu)镃 > B > A,這就出現(xiàn)了大問題。在多線程環(huán)境下,如果一個線程完成了擴容,Entry[ ] table會引用新數(shù)組Entry[ ] newTable,由于鏈表結點引用指向發(fā)生了倒序,那么另一個線程就會產生死鏈,在遍歷中產生死鏈會陷入死循環(huán)。
// 假設擴容前當前遍歷的鏈表為: A > B > C
// 分析t1線程發(fā)生死鏈的情況,有兩個線程 t1 和 t2,都在執(zhí)行擴容操作
 while(null != e) {
     
      // t2線程未擴容成功時,t1線程執(zhí)行,當 e = B,e.next一定為 C
      Entry<K,V> next = e.next;

      // t1線程發(fā)生上下文切換,此時t2線程先完成了擴容
      // 由于鏈表倒序為:C > B > A,當 e = B時,e.next的引用變?yōu)榱?A, next還是引用 C
      if (rehash) {
          e.hash = null == e.key ? 0 : hash(e.key);
      }
      int i = indexFor(e.hash, newCapacity);
      // B結點指向頭結點C,即 B > c > B,發(fā)生死鏈
      e.next = newTable[i];
      // B結點變?yōu)轭^結點
      newTable[i] = e;
   
     // 繼續(xù)遍歷,下一個節(jié)點變?yōu)镃,由于死鏈會變?yōu)樗姥h(huán)
     e = next;    
}

JDK8的HashMap為什么引入了紅黑樹的數(shù)據(jù)結構

  • ?紅黑樹是一種自平衡二叉搜索樹,并不是完全平衡的二叉樹。完全平衡二叉樹需要自旋多次才能達到平衡,而紅黑樹不需要多次自旋,同樣有很好的查詢性能,缺點是引入了結點顏色維護,變得更加復雜。

由淺入深了解HashMap源碼文章來源地址http://www.zghlxwxcb.cn/news/detail-412209.html

  • 《算法導論》中對于紅黑樹的定義:①每個結點不是紅結點就是黑結點 ;②根結點是黑色的;?③每個葉子節(jié)點(NIL)都是黑的;④如果一個節(jié)點是紅的,那么它的兩個兒子都是黑的;⑤對于任意一個結點,其到葉子節(jié)點(NIL)的所有路徑上的黑結點數(shù)都是相等的。HashMap中使用紅黑樹的目的是為了提升查詢效率,因為鏈表過長,會導致查詢效率變低。HashMap中的鏈表會在數(shù)組容量大于64和鏈表長度大于8時轉換為紅黑樹,同時紅黑樹也會在結點數(shù)小于等于6時退化為鏈表。?

到了這里,關于由淺入深了解HashMap源碼的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • Docker由淺入深(一)

    容器化技術介紹 介紹容器化之前,我們得先知道,為什么會出現(xiàn)容器化,容器化之前都經歷了什么 物理機時代 部署非常慢 成功很高 浪費資源 難于擴展與遷移 受制于硬件 虛擬化時代 在同一個物理機上安裝多個虛擬機,每個虛擬機安裝操作系統(tǒng)和應用, 虛擬機之間物理資源

    2024年02月03日
    瀏覽(39)
  • 由淺入深Netty代碼調優(yōu)

    由淺入深Netty代碼調優(yōu)

    序列化,反序列化主要用在消息正文的轉換上 序列化時,需要將 Java 對象變?yōu)橐獋鬏數(shù)臄?shù)據(jù)(可以是 byte[],或 json 等,最終都需要變成 byte[]) 反序列化時,需要將傳入的正文數(shù)據(jù)還原成 Java 對象,便于處理 目前的代碼僅支持 Java 自帶的序列化,反序列化機制,核心代碼如

    2024年02月05日
    瀏覽(23)
  • React - redux 使用(由淺入深)

    React - redux 使用(由淺入深)

    中文文檔: http://www.redux.org.cn/ 英文文檔: https://redux.js.org/ Github: https://github.com/reactjs/redux 可直接參照 目錄十 進行使用 react-redux redux 是一個專門用于做狀態(tài)管理的JS庫(不是react插件庫)。 它可以用在 react, angular, vue 等項目中, 但基本與 react 配合使用。 作用: 集中式管理 re

    2024年02月07日
    瀏覽(24)
  • 【個人筆記】由淺入深分析 ClickHouse

    項目中不少地方使用到ClickHouse,就對它做了一個相對深入一點的了解和研究。并對各種知識點及整理過程中的一些理解心得進行了匯總并分享出來,希望對其他同學能有幫助。 本文主要講解ClickHouse的特點、讀寫過程、存儲形式、索引、引擎、物化視圖等特性。 適合 入門和

    2024年01月20日
    瀏覽(28)
  • 【由淺入深學習MySQL】之索引進階

    【由淺入深學習MySQL】之索引進階

    本系列為:MySQL數(shù)據(jù)庫詳解,為千鋒資深教學老師獨家創(chuàng)作 致力于為大家講解清晰MySQL數(shù)據(jù)庫相關知識點,含有豐富的代碼案例及講解。如果感覺對大家有幫助的話,可以【關注】持續(xù)追更~ 文末有本文重點總結,技術類問題,也歡迎大家和我們溝通交流! 從今天開始本系列

    2024年02月05日
    瀏覽(22)
  • 手拉手Vue組件由淺入深

    手拉手Vue組件由淺入深

    組件 (Component) 是 Vue.js 最強大的功能之一,它是html、css、js等的一個聚合體,封裝性和隔離性非常強。 組件化開發(fā): ??? 1、將一個具備完整功能的項目的一部分分割多處使用 ??? 2、加快項目的進度 ??? 3、可以進行項目的復用 組件注冊分為:全局注冊和局部注冊 目錄

    2024年01月18日
    瀏覽(22)
  • 由淺入深介紹 Python Websocket 編程

    由淺入深介紹 Python Websocket 編程

    1.1 websocket 協(xié)議簡介 Websocket協(xié)議是對http的改進,可以實現(xiàn)client 與 server之間的雙向通信; websocket連接一旦建立就始終保持,直到client或server 中斷連接,彌補了http無法保持長連接的不足,方便了客戶端應用與服務器之間實時通信。 適用場景 html頁面實時更新, 客戶端的html頁面

    2024年02月03日
    瀏覽(22)
  • 由淺入深理解C#中的事件

    本文較長,給大家提供了目錄,可以直接看自己感興趣的部分。 前面介紹了C#中的委托,事件的很多部分都與委托類似。實際上,事件就像是專門用于某種特殊用途的簡單委托,事件包含了一個私有的委托,如下圖所示: 有關事件的私有委托需要了解的重要事項如下: 1、事

    2024年02月03日
    瀏覽(30)
  • Springboot3+EasyExcel由淺入深

    Springboot3+EasyExcel由淺入深

    環(huán)境介紹 技術棧 springboot3+easyexcel 軟件 版本 IDEA IntelliJ IDEA 2022.2.1 JDK 17 Spring Boot 3 EasyExcel是一個基于Java的、快速、簡潔、解決大文件內存溢出的Excel處理工具。 他能讓你在不用考慮性能、內存的等因素的情況下,快速完成Excel的讀、寫等功能。 官網https://easyexcel.opensource.ali

    2024年01月16日
    瀏覽(28)
  • 【由淺入深學MySQL】- MySQL連接查詢

    【由淺入深學MySQL】- MySQL連接查詢

    本系列為:MySQL數(shù)據(jù)庫詳解,為千鋒教育資深Java教學老師獨家創(chuàng)作 致力于為大家講解清晰MySQL數(shù)據(jù)庫相關知識點,含有豐富的代碼案例及講解。如果感覺對大家有幫助的話,可以【點個關注】持續(xù)追更~ 文末有重點總結和福利內容! 技術類問題,也歡迎大家和我們溝通交流!

    2024年02月05日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包