淺析C# Dictionary實現原理


  • 一、前言

  • 二、理論知識

    • 1、Hash 算法

    • 2、Hash 桶算法

    • 3、解決沖突算法

  • 三、Dictionary 實現

    • 1. Entry 結構體

    • 2. 其它關鍵私有變量

    • 3. Dictionary - Add 操作

    • 4. Dictionary - Find 操作

    • 5. Dictionary - Remove 操作

    • 6. Dictionary - Resize 操作(擴容)

    • 7. Dictionary - 再談 Add 操作

    • 8. Collection 版本控制

  • 四、參考文獻及總結


一、前言

本篇文章配圖以及文字其實整理出來很久了,但是由于各種各樣的原因推遲到現在才發出來,還有之前立 Flag 的《多線程編程》的筆記也都已經寫好了,只是說還比較糙,需要找個時間整理一下才能和大家見面。

對于 C#中的Dictionary類相信大家都不陌生,這是一個Collection(集合)類型,可以通過Key/Value(鍵值對的形式來存放數據;該類最大的優點就是它查找元素的時間復雜度接近O(1),實際項目中常被用來做一些數據的本地緩存,提升整體效率。

那么是什么樣的設計能使得Dictionary類能實現O(1)的時間復雜度呢?那就是本篇文章想和大家討論的東西;這些都是個人的一些理解和觀點,如有表述不清楚、錯誤之處,請大家批評指正,共同進步。

二、理論知識

對于 Dictionary 的實現原理,其中有兩個關鍵的算法,一個是Hash算法,一個是用于應對 Hash 碰撞沖突解決算法。

1、Hash 算法

Hash 算法是一種數字摘要算法,它能將不定長度的二進制數據集給映射到一個較短的二進制長度數據集,常見的 MD5 算法就是一種 Hash 算法,通過 MD5 算法可對任何數據生成數字摘要。而實現了 Hash 算法的函數我們叫她Hash 函數。Hash 函數有以下幾點特征。

  1. 相同的數據進行 Hash 運算,得到的結果一定相同。HashFunc(key1) == HashFunc(key1)

  2. 不同的數據進行 Hash 運算,其結果也可能會相同,(Hash 會產生碰撞)。key1 != key2 => HashFunc(key1) == HashFunc(key2).

  3. Hash 運算時不可逆的,不能由 key 獲取原始的數據。key1 => hashCode但是hashCode =\=> key1

下圖就是 Hash 函數的一個簡單說明,任意長度的數據通過 HashFunc 映射到一個較短的數據集中。

f0a5ac37feaca5d56ef076a8b8b536ad.png
1548491108167

關于 Hash 碰撞下圖很清晰的就解釋了,可從圖中得知Sandra DeeJohn Smith通過 hash 運算后都落到了02的位置,產生了碰撞和沖突。341cd024897cbf6f34987b2aaaa8ac57.png常見的構造 Hash 函數的算法有以下幾種。

**1. 直接尋址法:**取 keyword 或 keyword 的某個線性函數值為散列地址。即 H(key)=key 或 H(key) = a?key + b,當中 a 和 b 為常數(這樣的散列函數叫做自身函數)

**2. 數字分析法:**分析一組數據,比方一組員工的出生年月日,這時我們發現出生年月日的前幾位數字大體同樣,這種話,出現沖突的幾率就會非常大,可是我們發現年月日的后幾位表示月份和詳細日期的數字區別非常大,假設用后面的數字來構成散列地址,則沖突的幾率會明顯減少。因此數字分析法就是找出數字的規律,盡可能利用這些數據來構造沖突幾率較低的散列地址。

**3. 平方取中法:**取 keyword 平方后的中間幾位作為散列地址。

**4. 折疊法:**將 keyword 切割成位數同樣的幾部分,最后一部分位數能夠不同,然后取這幾部分的疊加和(去除進位)作為散列地址。

**5. 隨機數法:**選擇一隨機函數,取 keyword 的隨機值作為散列地址,通經常使用于 keyword 長度不同的場合。

**6. 除留余數法:**取 keyword 被某個不大于散列表表長 m 的數 p 除后所得的余數為散列地址。即 H(key) = key MOD p, p<=m。不僅能夠對 keyword 直接取模,也可在折疊、平方取中等運算之后取模。對 p 的選擇非常重要,一般取素數或 m,若 p 選的不好,容易產生碰撞.

2、Hash 桶算法

說到 Hash 算法大家就會想到Hash 表,一個 Key 通過 Hash 函數運算后可快速的得到 hashCode,通過 hashCode 的映射可直接 Get 到 Value,但是 hashCode 一般取值都是非常大的,經常是 2^32 以上,不可能對每個 hashCode 都指定一個映射。

因為這樣的一個問題,所以人們就將生成的 HashCode 以分段的形式來映射,把每一段稱之為一個Bucket(桶),一般常見的 Hash 桶就是直接對結果取余。

假設將生成的 hashCode 可能取值有 2^32 個,然后將其切分成一段一段,使用8個桶來映射,那么就可以通過bucketIndex = HashFunc(key1) % 8這樣一個算法來確定這個 hashCode 映射到具體的哪個桶中。

大家可以看出來,通過 hash 桶這種形式來進行映射,所以會加劇 hash 的沖突。

3、解決沖突算法

對于一個 hash 算法,不可避免的會產生沖突,那么產生沖突以后如何處理,是一個很關鍵的地方,目前常見的沖突解決算法有拉鏈法(Dictionary 實現采用的)、開放定址法、再 Hash 法、公共溢出分區法,本文只介紹拉鏈法與再 Hash 法,對于其它算法感興趣的同學可參考文章最后的參考文獻。

**1. 拉鏈法:**這種方法的思路是將產生沖突的元素建立一個單鏈表,并將頭指針地址存儲至 Hash 表對應桶的位置。這樣定位到 Hash 表桶的位置后可通過遍歷單鏈表的形式來查找元素。

**2. 再 Hash 法:**顧名思義就是將 key 使用其它的 Hash 函數再次 Hash,直到找到不沖突的位置為止。

對于拉鏈法有一張圖來描述,通過在沖突位置建立單鏈表,來解決沖突。

b66385e2fccebbe36c2e67dd8ea345b9.png
1548485607652

三、Dictionary 實現

Dictionary 實現我們主要對照源碼來解析,目前對照源碼的版本是**.Net Framwork 4.7**。地址可戳一戳這個鏈接 源碼地址:Link[1]

這一章節中主要介紹 Dictionary 中幾個比較關鍵的類和對象,然后跟著代碼來走一遍插入、刪除和擴容的流程,相信大家就能理解它的設計原理。

1. Entry 結構體

首先我們引入Entry這樣一個結構體,它的定義如下代碼所示。這是 Dictionary 種存放數據的最小單位,調用Add(Key,Value)方法添加的元素都會被封裝在這樣的一個結構體中。

private?struct?Entry?{public?int?hashCode;????//?除符號位以外的31位hashCode值,?如果該Entry沒有被使用,那么為-1public?int?next;????????//?下一個元素的下標索引,如果沒有下一個就為-1public?TKey?key;????????//?存放元素的鍵public?TValue?value;????//?存放元素的值
}

2. 其它關鍵私有變量

除了 Entry 結構體外,還有幾個關鍵的私有變量,其定義和解釋如下代碼所示。

private?int[]?buckets;??//?Hash桶
private?Entry[]?entries;?//?Entry數組,存放元素
private?int?count;???//?當前entries的index位置
private?int?version;??//?當前版本,防止迭代過程中集合被更改
private?int?freeList;??//?被刪除Entry在entries中的下標index,這個位置是空閑的
private?int?freeCount;??//?有多少個被刪除的Entry,有多少個空閑的位置
private?IEqualityComparer<TKey>?comparer;?//?比較器
private?KeyCollection?keys;??//?存放Key的集合
private?ValueCollection?values;??//?存放Value的集合

上面代碼中,需要注意的是buckets、entries這兩個數組,這是實現 Dictionary 的關鍵。

3. Dictionary - Add 操作

經過上面的分析,相信大家還不是特別明白為什么需要這么設計,需要這么做。那我們現在來走一遍 Dictionary 的 Add 流程,來體會一下。

首先我們用圖的形式來描述一個 Dictionary 的數據結構,其中只畫出了關鍵的地方。桶大小為 4 以及 Entry 大小也為 4 的一個數據結構。

51af540ec0ac026c4a2e8c76cad550b0.png
1548491185593

然后我們假設需要執行一個Add操作,dictionary.Add("a","b"),其中key = "a",value = "b"

  1. 根據key的值,計算出它的 hashCode。我們假設"a"的 hash 值為 6(GetHashCode("a") = 6)。

  2. 通過對 hashCode 取余運算,計算出該 hashCode 落在哪一個 buckets 桶中。現在桶的長度(buckets.Length)為 4,那么就是6 % 4最后落在index為 2 的桶中,也就是buckets[2]

  3. 避開一種其它情況不談,接下來它會將hashCode、key、value等信息存入entries[count]中,因為count位置是空閑的;繼續count++指向下一個空閑位置。上圖中第一個位置,index=0 就是空閑的,所以就存放在entries[0]的位置。

  4. Entry的下標entryIndex賦值給buckets中對應下標的bucket。步驟 3 中是存放在entries[0]的位置,所以buckets[2]=0

  5. 最后version++,集合發生了變化,所以版本需要+1。只有增加、替換和刪除元素才會更新版本

    上文中的步驟 1~5 只是方便大家理解,實際上有一些偏差,后文再談 Add 操作小節中會補充。

完成上面 Add 操作后,數據結構更新成了下圖這樣的形式。

34abcef5ef11fbaebb80dde118f1f386.png
1548492100757

這樣是理想情況下的操作,一個 bucket 中只有一個 hashCode 沒有碰撞的產生,但是實際上是會經常產生碰撞;那么 Dictionary 類中又是如何解決碰撞的呢。

我們繼續執行一個Add操作,dictionary.Add("c","d"),假設GetHashCode(“c”)=6,最后6 % 4 = 2。最后桶的index也是 2,按照之前的步驟 1~3是沒有問題的,執行完后數據結構如下圖所示。

f692a8b18a008a7cff371992188c526e.png
1548493287583

如果繼續執行步驟 4那么buckets[2] = 1,然后原來的buckets[2]=>entries[0]的關系就會丟失,這是我們不愿意看到的。現在 Entry 中的next就發揮大作用了。

如果對應的buckets[index]有其它元素已經存在,那么會執行以下兩條語句,讓新的entry.next指向之前的元素,讓buckets[index]指向現在的新的元素,就構成了一個單鏈表。

entries[index].next?=?buckets[targetBucket];
...
buckets[targetBucket]?=?index;

實際上步驟 4也就是做一個這樣的操作,并不會去判斷是不是有其它元素,因為buckets中桶初始值就是-1,不會造成問題。

經過上面的步驟以后,數據結構就更新成了下圖這個樣子。

67db61fe79c171e71cdc1cfe7eb5a4e5.png
1548494357566

4. Dictionary - Find 操作

為了方便演示如何查找,我們繼續 Add 一個元素dictionary.Add("e","f")GetHashCode(“e”) = 7; 7% buckets.Length=3,數據結構如下所示。

06e1931124edd96779c0a9e79d5d19e2.png
1548494583006

假設我們現在執行這樣一條語句dictionary.GetValueOrDefault("a"),會執行以下步驟.

  1. 獲取 key 的 hashCode,計算出所在的桶位置。我們之前提到,"a"的hashCode=6,所以最后計算出來targetBucket=2

  2. 通過buckets[2]=1找到entries[1],比較 key 的值是否相等,相等就返回entryIndex,不想等就繼續entries[next]查找,直到找到 key 相等元素或者next == -1的時候。這里我們找到了key == "a"的元素,返回entryIndex=0

  3. 如果entryIndex >= 0那么返回對應的entries[entryIndex]元素,否則返回default(TValue)。這里我們直接返回entries[0].value

整個查找的過程如下圖所示.

5b626392118c2e957860cd32cb5e0243.png
1548495296415

將查找的代碼摘錄下來,如下所示。

//?尋找Entry元素的位置
private?int?FindEntry(TKey?key)?{if(?key?==?null)?{ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);}if?(buckets?!=?null)?{int?hashCode?=?comparer.GetHashCode(key)?&?0x7FFFFFFF;?//?獲取HashCode,忽略符號位//?int?i?=?buckets[hashCode?%?buckets.Length]?找到對應桶,然后獲取entry在entries中位置//?i?>=?0;?i?=?entries[i].next?遍歷單鏈表for?(int?i?=?buckets[hashCode?%?buckets.Length];?i?>=?0;?i?=?entries[i].next)?{//?找到就返回了if?(entries[i].hashCode?==?hashCode?&&?comparer.Equals(entries[i].key,?key))?return?i;}}return?-1;
}
...
internal?TValue?GetValueOrDefault(TKey?key)?{int?i?=?FindEntry(key);//?大于等于0代表找到了元素位置,直接返回value//?否則返回該類型的默認值if?(i?>=?0)?{return?entries[i].value;}return?default(TValue);
}

5. Dictionary - Remove 操作

前面已經向大家介紹了增加、查找,接下來向大家介紹 Dictionary 如何執行刪除操作。我們沿用之前的 Dictionary 數據結構。

f243f824302990126e6d939f29ccbf00.png
1548494583006

刪除前面步驟和查找類似,也是需要找到元素的位置,然后再進行刪除的操作。

我們現在執行這樣一條語句dictionary.Remove("a"),hashFunc 運算結果和上文中一致。步驟大部分與查找類似,我們直接看摘錄的代碼,如下所示。

public?bool?Remove(TKey?key)?{if(key?==?null)?{ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);}if?(buckets?!=?null)?{//?1.?通過key獲取hashCodeint?hashCode?=?comparer.GetHashCode(key)?&?0x7FFFFFFF;//?2.?取余獲取bucket位置int?bucket?=?hashCode?%?buckets.Length;//?last用于確定是否當前bucket的單鏈表中最后一個元素int?last?=?-1;//?3.?遍歷bucket對應的單鏈表for?(int?i?=?buckets[bucket];?i?>=?0;?last?=?i,?i?=?entries[i].next)?{if?(entries[i].hashCode?==?hashCode?&&?comparer.Equals(entries[i].key,?key))?{//?4.?找到元素后,如果last<?0,代表當前是bucket中最后一個元素,那么直接讓bucket內下標賦值為?entries[i].next即可if?(last?<?0)?{buckets[bucket]?=?entries[i].next;}else?{//?4.1?last不小于0,代表當前元素處于bucket單鏈表中間位置,需要將該元素的頭結點和尾節點相連起來,防止鏈表中斷entries[last].next?=?entries[i].next;}//?5.?將Entry結構體內數據初始化entries[i].hashCode?=?-1;//?5.1?建立freeList單鏈表entries[i].next?=?freeList;entries[i].key?=?default(TKey);entries[i].value?=?default(TValue);//?*6.?關鍵的代碼,freeList等于當前的entry位置,下一次Add元素會優先Add到該位置freeList?=?i;freeCount++;//?7.?版本號+1version++;return?true;}}}return?false;
}

執行完上面代碼后,數據結構就更新成了下圖所示。需要注意varsion、freeList、freeCount的值都被更新了。

5f7f46ad4d5630c72f82d2904958c691.png
1548496815179

6. Dictionary - Resize 操作(擴容)

有細心的小伙伴可能看過了Add操作以后就想問了,buckets、entries不就是兩個數組么,那萬一數組放滿了怎么辦?接下來就是我所要介紹的**Resize(擴容)**這樣一種操作,對我們的buckets、entries進行擴容。

6.1 擴容操作的觸發條件

首先我們需要知道在什么情況下,會發生擴容操作;**第一種情況自然就是數組已經滿了,沒有辦法繼續存放新的元素。**如下圖所示的情況。

7f6733fa0399ffa868ba13717dc99de1.png
1548498710430

從上文中大家都知道,Hash 運算會不可避免的產生沖突,Dictionary 中使用拉鏈法來解決沖突的問題,但是大家看下圖中的這種情況。

c09686b303fb8d1f02a52e1fc99824f0.png
1548498901496

所有的元素都剛好落在buckets[3]上面,結果就是導致了時間復雜度 O(n),查找性能會下降;所以**第二種,Dictionary 中發生的碰撞次數太多,會嚴重影響性能,**也會觸發擴容操作。

目前.Net Framwork 4.7 中設置的碰撞次數閾值為 100.

public?const?int?HashCollisionThreshold?=?100;

6.2 擴容操作如何進行

為了給大家演示的清楚,模擬了以下這種數據結構,大小為 2 的 Dictionary,假設碰撞的閾值為 2;現在觸發 Hash 碰撞擴容。

050813b76eb1a5734879d7b6d89b38e8.png
1548499708530

開始擴容操作。

1.申請兩倍于現在大小的 buckets、entries > 2.將現有的元素拷貝到新的 entries

完成上面兩步操作后,新數據結構如下所示。

9ff6de307c7f9203d266db74eec65ddf.png
1548499785441

3、如果是 Hash 碰撞擴容,使用新 HashCode 函數重新計算 Hash 值

上文提到了,這是發生了 Hash 碰撞擴容,所以需要使用新的 Hash 函數計算 Hash 值。新的 Hash 函數并一定能解決碰撞的問題,有可能會更糟,像下圖中一樣的還是會落在同一個bucket上。

41121af1766fb98154d76b2884b5223d.png
1548500174305

4、對 entries 每個元素 bucket = newEntries[i].hashCode % newSize 確定新 buckets 位置

**5、重建 hash 鏈,newEntries[i].next=buckets[bucket]; buckets[bucket]=i; **

因為buckets也擴充為兩倍大小了,所以需要重新確定hashCode在哪個bucket中;最后重新建立 hash 單鏈表.

363c7399a3dcbbe3c3aac23188f98329.png
1548500290419

這就完成了擴容的操作,如果是達到Hash 碰撞閾值觸發的擴容可能擴容后結果會更差。

在 JDK 中,HashMap如果碰撞的次數太多了,那么會將單鏈表轉換為紅黑樹提升查找性能。目前**.Net Framwork中還沒有這樣的優化,.Net Core中已經有了類似的優化,以后有時間在分享.Net Core**的一些集合實現。

每次擴容操作都需要遍歷所有元素,會影響性能。所以創建 Dictionary 實例時最好設置一個預估的初始大小。

private?void?Resize(int?newSize,?bool?forceNewHashCodes)?{Contract.Assert(newSize?>=?entries.Length);//?1.?申請新的Buckets和entriesint[]?newBuckets?=?new?int[newSize];for?(int?i?=?0;?i?<?newBuckets.Length;?i++)?newBuckets[i]?=?-1;Entry[]?newEntries?=?new?Entry[newSize];//?2.?將entries內元素拷貝到新的entries總Array.Copy(entries,?0,?newEntries,?0,?count);//?3.?如果是Hash碰撞擴容,使用新HashCode函數重新計算Hash值if(forceNewHashCodes)?{for?(int?i?=?0;?i?<?count;?i++)?{if(newEntries[i].hashCode?!=?-1)?{newEntries[i].hashCode?=?(comparer.GetHashCode(newEntries[i].key)?&?0x7FFFFFFF);}}}//?4.?確定新的bucket位置//?5.?重建Hahs單鏈表for?(int?i?=?0;?i?<?count;?i++)?{if?(newEntries[i].hashCode?>=?0)?{int?bucket?=?newEntries[i].hashCode?%?newSize;newEntries[i].next?=?newBuckets[bucket];newBuckets[bucket]?=?i;}}buckets?=?newBuckets;entries?=?newEntries;
}

7. Dictionary - 再談 Add 操作

在我們之前的Add操作步驟中,提到了這樣一段話,這里提到會有一種其它的情況,那就是有元素被刪除的情況。

  1. 避開一種其它情況不談,接下來它會將hashCode、key、value等信息存入entries[count]中,因為count位置是空閑的;繼續count++指向下一個空閑位置。上圖中第一個位置,index=0 就是空閑的,所以就存放在entries[0]的位置。

因為count是通過自增的方式來指向entries[]下一個空閑的entry,如果有元素被刪除了,那么在count之前的位置就會出現一個空閑的entry;如果不處理,會有很多空間被浪費。

這就是為什么Remove操作會記錄freeList、freeCount,就是為了將刪除的空間利用起來。實際上Add操作會優先使用freeList的空閑entry位置,摘錄代碼如下。

private?void?Insert(TKey?key,?TValue?value,?bool?add){if(?key?==?null?)?{ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);}if?(buckets?==?null)?Initialize(0);//?通過key獲取hashCodeint?hashCode?=?comparer.GetHashCode(key)?&?0x7FFFFFFF;//?計算出目標bucket下標int?targetBucket?=?hashCode?%?buckets.Length;//?碰撞次數int?collisionCount?=?0;for?(int?i?=?buckets[targetBucket];?i?>=?0;?i?=?entries[i].next)?{if?(entries[i].hashCode?==?hashCode?&&?comparer.Equals(entries[i].key,?key))?{//?如果是增加操作,遍歷到了相同的元素,那么拋出異常if?(add)?{ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);}//?如果不是增加操作,那可能是索引賦值操作?dictionary["foo"]?=?"foo"//?那么賦值后版本++,退出entries[i].value?=?value;version++;return;}//?每遍歷一個元素,都是一次碰撞collisionCount++;}int?index;//?如果有被刪除的元素,那么將元素放到被刪除元素的空閑位置if?(freeCount?>?0)?{index?=?freeList;freeList?=?entries[index].next;freeCount--;}else?{//?如果當前entries已滿,那么觸發擴容if?(count?==?entries.Length){Resize();targetBucket?=?hashCode?%?buckets.Length;}index?=?count;count++;}//?給entry賦值entries[index].hashCode?=?hashCode;entries[index].next?=?buckets[targetBucket];entries[index].key?=?key;entries[index].value?=?value;buckets[targetBucket]?=?index;//?版本號++version++;//?如果碰撞次數大于設置的最大碰撞次數,那么觸發Hash碰撞擴容if(collisionCount?>?HashHelpers.HashCollisionThreshold?&&?HashHelpers.IsWellKnownEqualityComparer(comparer)){comparer?=?(IEqualityComparer<TKey>)?HashHelpers.GetRandomizedEqualityComparer(comparer);Resize(entries.Length,?true);}
}

上面就是完整的Add代碼,還是很簡單的對不對?

8. Collection 版本控制

在上文中一直提到了version這個變量,在每一次新增、修改和刪除操作時,都會使version++;那么這個version存在的意義是什么呢?

首先我們來看一段代碼,這段代碼中首先實例化了一個 Dictionary 實例,然后通過foreach遍歷該實例,在foreach代碼塊中使用dic.Remove(kv.Key)刪除元素。

dc7ed71fe45a7550a660c1895fade5ca.png
1548504444217

結果就是拋出了System.InvalidOperationException:"Collection was modified..."這樣的異常,迭代過程中不允許集合出現變化。如果在 Java 中遍歷直接刪除元素,會出現詭異的問題,所以.Net 中就使用了version來實現版本控制。

那么如何在迭代過程中實現版本控制的呢?我們看一看源碼就很清楚的知道。

9692436e42223d91bb85a0a32c5a273f.png
1548504844162

在迭代器初始化時,就會記錄dictionary.version版本號,之后每一次迭代過程都會檢查版本號是否一致,如果不一致將拋出異常。

這樣就避免了在迭代過程中修改了集合,造成很多詭異的問題。

四、參考文獻及總結

本文在編寫過程中,主要參考了以下文獻,在此感謝其作者在知識分享上作出的貢獻!

  1. http://www.cnblogs.com/mengfanrong/p/4034950.html

  2. https://en.wikipedia.org/wiki/Hash_table

  3. https://www.cnblogs.com/wuchaodzxx/p/7396599.html

  4. https://www.cnblogs.com/liwei2222/p/8013367.html

  5. https://referencesource.microsoft.com/#mscorlib/system/collections/generic/dictionary.cs,fd1acf96113fbda9

筆者水平有限,如果錯誤歡迎各位批評指正!

參考資料

[1]

Link: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/dictionary.cs,d3599058f8d79be0

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

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

相關文章

對特朗普獲勝感到意外? 那你是被社交媒體迷惑了

北京時間11月10日消息&#xff0c;據外媒報道&#xff0c;昨天曠日持久的美國總統選戰終于告一段落&#xff0c;特朗普的獲勝讓民調徹底成了一張廢紙&#xff0c;而早就在Facebook上提前歡慶希拉里勝利的人則徹底蒙圈了&#xff0c;就連萬里之外的中國吃瓜群眾們也開始追著許多…

貓晚流量再創記錄,阿里云直播方案護航優酷2500萬用戶體驗

2019獨角獸企業重金招聘Python工程師標準>>> 對“剁手黨而言&#xff0c;天貓雙11早已經超越了簡單的“買買買”&#xff0c;更是一場邊看邊玩的狂歡盛宴。今年的天貓雙11狂歡夜晚會&#xff08;簡稱“貓晚”&#xff09;在上海舉辦&#xff0c;這臺兼具年輕潮流與國…

python實現二叉樹和它的七種遍歷

介紹&#xff1a; 樹是數據結構中非常重要的一種&#xff0c;主要的用途是用來提高查找效率&#xff0c;對于要重復查找的情況效果更佳&#xff0c;如二叉排序樹、FP-樹。另外可以用來提高編碼效率&#xff0c;如哈弗曼樹。 代碼&#xff1a; 用python實現樹的構造和幾種遍歷算…

.NET性能系列文章二:Newtonsoft.Json vs System.Text.Json

微軟終于追上了&#xff1f;圖片來自 Glenn Carstens-Peters[1]Unsplash[2]歡迎來到.NET 性能系列的另一章。這個系列的特點是對.NET 世界中許多不同的主題進行研究、基準和比較。正如標題所說的那樣&#xff0c;重點在于使用最新的.NET7 的性能。你將看到哪種方法是實現特定主…

android gpu平板 推薦,性能強的不像話,最強安卓平板華為平板M6上手

原標題&#xff1a;性能強的不像話&#xff0c;最強安卓平板華為平板M6上手你為什么買平板電腦&#xff1f;當這一問題問出以后&#xff0c;許多朋友的表情都很微妙&#xff0c;隨后大概率的回答則相當統一&#xff1a;"我買平板干嘛&#xff1f;"。其實得到這樣一個…

【Python】HackBack(獲取暴力破解服務器密碼的IP來源)

1、前言 又在0x00sec上翻到好東東。 https://0x00sec.org/t/python-hackback-updated/882 帖子里的腳本會得到那些暴力服務器密碼失敗的IP和用戶名&#xff0c;并且使用shodan api做一個溯源定位。 #!/usr/bin/python3.4 import re import urllib.request import json log_path…

企業應用“數據優先”革命的下一個主戰場:安全與運營

根據IDC發布的2015年全球CIO日程預測&#xff0c;80%的CIO將提供一個實現創新和改善業務決策的新體系架構。 大數據時代&#xff0c;企業軟件市場正在經歷一次大遷移&#xff0c;數以十億計的企業IT支出預算將投向“數據優先”應用&#xff0c;而不是長久以來以業務流程和工作流…

給Web開發人員的以太坊入坑指南

以太坊現在各種學習資料數不勝數&#xff0c;但由于以太坊正處于飛速發展階段&#xff0c;有些學習資料很快就過時了。所以想找到有價值的資料無異于大海撈針。我費了很大功夫&#xff0c;才建立起對以太坊的整體認識&#xff0c;搞清楚它的工作機制。我相信很多躍躍欲試的開發…

和碩看重物聯網大勢 程建中:從擅長領域出發

物聯網(IoT)前景可期已是全球科技產業的共識&#xff0c;但是如何真正找出到位的商機&#xff0c;卻考驗產業鏈業者的智能。蘋果iPhone代工廠和碩聯合科技執行長程建中表示&#xff0c;物聯網與大數據相關應用商機看俏&#xff0c;物聯網筑的夢比網際網路還大&#xff0c;當年網…

html選擇文本框后提示消失,兩種方法實現文本框輸入內容提示消失

第一種方法&#xff1a;基于HTML5 input標簽的新特性 - placeholder 。另外&#xff0c;x-webkit-speech 屬性可以實現語音輸入功能。第二種方法&#xff1a;用span模擬&#xff0c;定位span&#xff0c;借助JS鍵盤事件判斷輸入&#xff0c;確定span里的內容顯示隱藏。無標題文…

TensorFlow基本計算單元——變量

# -*- coding: utf-8 -*- import tensorflow as tf a 3 # 創建變量 w tf.Variable([[0.5, 1.0]]) #行向量 x tf.Variable([[2.0], [1.0]]) y tf.matmul(w, x) #矩陣相乘 print(y) # Tensor("MatMul:0", shape(1, 1), dtypefloat32)init_op tf.global_variables…

程序人生:織夢dedecms后臺/會員驗證碼關閉

dedecms默認是所有的功能幾乎只要用到驗證碼的地方我們都需要驗證的&#xff0c;如果要關閉一些驗證功能我們可以參考下面的教程&#xff0c;這里介紹了關閉后臺&#xff0c;留言板&#xff0c;會員系統等驗證碼功能關閉了。提示&#xff1a;支持DedeCMS V5.6 以上的所有版本取…

html中圖片的屬性優化,Html標簽元素在SEO中的優化方式(二)

接上html標簽元素在SEO中的優化方式(一)中對HTML界面的介紹&#xff0c;我們今天繼續補充HTML標簽的SEO優化方式在內容中有幾個值得去研究一下的優化元素--導航和內部鏈接&#xff1a;很明顯的一點&#xff0c;建立導航會使搜索引擎可以容易的確定網站結構&#xff0c;但是很多…

Gartner認為安全性將取代成本和敏捷性成為政府部門采用云服務的首要原因

全球領先的信息技術研究和顧問公司Gartner表示&#xff0c;公有云如今具備可擴展性、計算威力、海量存儲和安全性&#xff0c;可打造更好的政府數字化平臺并滿足對業績和價值不斷增長的期望值。 Gartner預計到2018年&#xff0c;提升的安全性將取代成本節約和敏捷性成為政府部門…

一款簡單的縮放拖拽圖片控件

本文介紹一個針對 .NET 桌面應用程序的獨立圖片縮放拖拽顯示控件 SQPhoto[1]。SQPhoto 是一個 Windows 桌面應用的組件&#xff0c;支持 .NET6 和 .NET Framework 4.6 。基于 PictureBox 的圖片展示工具&#xff0c;增加了拖動和縮放功能&#xff0c;便于在某些場景下的圖片展…

安卓虛擬機與Hyper-V沖突

經過各種經驗&#xff0c;哪個安卓虛擬機跟Hyper-V都存在著沖突。 解決方案一 程序中卸載Hyper-V&#xff0c;之后還要再配置太麻煩。 解決方案二 1、關掉Hyper-V的啟動項&#xff0c;命令如下。 bcdedit /set hypervisorlaunchtype off 重啟在開安卓模擬器藍屏就沒有了。 2、重…

共軌之家獲吉利家族基金新一輪融資 5個月前曾獲磐霖資本領投A輪融資

11月27日消息&#xff0c;近日&#xff0c;國內最大商用車技術服務平臺共軌之家對外宣布完成新一輪融資。本輪融資由吉利家族基金投資&#xff0c;未來將推進共軌之家占領商用車后市場。 成立于2014年的共軌之家&#xff0c;以商用車技術知識科普社區起家&#xff0c;積累起國…

光伏產業的發展推動太陽能組件技術進步

從全球能源發展趨勢的角度來看&#xff0c;太陽能等可再生能源勢必逐步替代不可再生的傳統能源&#xff0c;光伏產業將在全球各國的經濟發展中扮演重要的角色。大力開發和利用光伏產業&#xff0c;對于優化我國能源結構&#xff0c;優化環境&#xff0c;保證我國社會經濟可持續…

HTML怎么讓div全透明,設置div為透明 怎樣才讓div里面的div不透明?

#a{ background:#FFCC33; filter:alpha(opacity:0); width: 300px; heig#a{background:#FFCC33; filter:alpha(opacity50); /*支持 IE 瀏覽器*/-moz-opacity:0.50; /*支持 FireFox 瀏覽器*/opacity:0.50; /*支持 Chrome, Opera, Safari 等瀏覽器*/width: 300px;height:300px;}還…

k64 datasheet學習筆記12---System Integration Module (SIM)

1.前言 Features of the SIM include: System clocking configuration&#xff08;1&#xff09;System clock divide values&#xff08;2&#xff09; Architectural clock gating control&#xff08;3&#xff09; USB clock selection and divide values&#xff08;4&…