從內部剖析C# 集合之--Dictionary


 Dictionary和hashtable用法有點相似,他們都是基於鍵值對的數據集合,但實際上他們內部的實現原理有很大的差異,

先簡要概述一下他們主要的區別,稍后在分析Dictionary內部實現的大概原理。

區別:1,Dictionary支持泛型,而Hashtable不支持。

        2,Dictionary沒有裝填因子(Load Facto)概念,當容量不夠時才擴容(擴容跟Hashtable一樣,也是兩倍於當前容量最小素數),Hashtable是“已裝載元素”與”bucket數組長度“大於裝載因子時擴容。

        3,Dictionary內部的存儲value的數組按先后插入的順序排序,Hashtable不是。

       4,當不發生碰撞時,查找Dictionary需要進行兩次索引定位,Hashtable需一次,。

 Dictionary采用除法散列法來計算存儲地址,想詳細了解的可以百度一下,簡單來說就是其內部有兩個數組:buckets數組和entries數組(entries是一個Entry結構數組),entries有一個next用來模擬鏈表,該字段存儲一個int值,指向下一個存儲地址(實際就是bukets數組的索引),當沒有發生碰撞時,該字段為-1,發生了碰撞則存儲一個int值,該值指向bukets數組.

 

下面跟上次一樣,按正常使用Dictionary時,看內部是如何實現的。

一,實例化一個Dictionary, Dictionary<string,string> dic=new Dictionary<string,string>();

    a,調用Dictionary默認無參構造函數。

    b,初始化Dictionary內部數組容器:buckets int[]和entries<T,V>[],分別分配長度3。(內部有一個素數數組:3,7,11,17....如圖:);

  二,向dic添加一個值,dic.add("a","abc");

     a,將bucket數組和entries數組擴容3個長度。

     b,計算"a"的哈希值,

     c,然后與bucket數組長度(3)進行取模計算,假如結果為:2

     d,因為a是第一次寫入,則自動將a的值賦值到entriys[0]的key,同理將"abc"賦值給entriys[0].value,將上面b步驟的哈希值賦值給entriys[0].hashCode,

       entriys[0].next 賦值為-1,hashCode賦值b步驟計算出來的哈希值。

    e,在bucket[2]存儲0。

三,通過key獲取對應的value,  var v=dic["a"];

   a, 先計算"a"的哈希值,假如結果為2,

   b,根據上一步驟結果,找到buckets數組索引為2上的值,假如該值為0.

   c, 找到到entriys數組上索引為0的key,

         1),如果該key值和輸入的的“a”字符相同,則對應的value值就是需要查找的值。

         2) ,如果該key值和輸入的"a"字符不相同,說明發生了碰撞,這時獲取對應的next值,根據next值定位buckets數組(buckets[next]),然后獲取對應buckets上存儲的值在定位到entriys數組上,......,一直到找到為止。

         3),如果該key值和輸入的"a"字符不相同並且對應的next值為-1,則說明Dictionary不包含字符“a”。

 

Dictionary里的其他方法就不說了,各位可以自己去看源碼,下面來通過實驗來對比Hashtable和Dictionary的添加和查找性能,

1,添加元素速度測評。

     循環5次,每次內部在循環10次取平均值,PS:代碼中如有不公平的地方望各位指出,本人知錯就改。

 a,值類型

static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(string.Format("第{0}次執行:", i + 1));
                Add();
                Console.WriteLine("-------華麗的分分隔線---------");
            }
            
            Console.ReadKey();
        }
        public static void Add()
        {
            Hashtable ht = new Hashtable();
            Stopwatch st = new Stopwatch();

            long ticks1 = 0;
            for (int j = 0; j < 10; j++)
            {
                st.Reset();
                st.Start();
                for (int i = 0; i < 1000000; i++)
                {
                    ht.Add(i, i);
                }
                st.Stop();
                ticks1 += st.ElapsedTicks;
                ht.Clear();
            }

            Console.WriteLine(string.Format("Hashtable添加:{0}個元素,消耗:{1}", 1000000, ticks1 / 10));

            Dictionary<int, int> dic = new Dictionary<int, int>();
            ticks1 = 0;
            for (int j = 0; j < 10; j++)
            {
                st.Reset();
                st.Start();
                for (int i = 0; i < 1000000; i++)
                {
                    dic.Add(i, i);
                }
                st.Stop();
                ticks1 += st.ElapsedTicks;
                dic.Clear();
            }
            
            Console.WriteLine(string.Format("Dictionary添加:{0}個元素,消耗:{1}", 1000000, st.ElapsedTicks));
        }
View Code

 結果:

通過運行結果來看,HashTable 速度明顯慢於Dictionary,相差一個數量級。我個人分析原因可能為:

   a,Hashtable不支持泛型,我向你添加的int類型會發生裝箱操作,而Dictionary支持泛型。

   b,Hashtable在擴容時會先new一個更大的數組,然后將原來的數據復制到新的數組里,還需對新數組里的key重新哈希計算(這可能是最性能影響最大的因素)。而Dictionary不會這樣。

b,引用類型

 static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(string.Format("第{0}次執行",i+1));
                Add();
                Console.WriteLine("--------華麗的分隔線------");
            }
           
            
            Console.ReadKey();
        }


        public static void Add()
        {
            Hashtable ht = new Hashtable();
            Stopwatch st = new Stopwatch();

            long ticks1 = 0;
            for (int j = 0; j < 10; j++)
            {
                st.Reset();
                st.Start();
                for (int i = 0; i < 1000000; i++)
                {
                    ht.Add(i.ToString(), i.ToString());
                }
                st.Stop();
                ticks1 += st.ElapsedTicks;
                ht.Clear();
            }

            Console.WriteLine(string.Format("Hashtable添加:{0}個元素,消耗:{1}", 1000000, ticks1 / 10));

            Dictionary<string, string> dic = new Dictionary<string, string>();
            ticks1 = 0;
            for (int j = 0; j < 10; j++)
            {
                st.Reset();
                st.Start();
                for (int i = 0; i < 1000000; i++)
                {
                    dic.Add(i.ToString(), i.ToString());
                }
                st.Stop();
                ticks1 += st.ElapsedTicks;
                dic.Clear();
            }
            
            Console.WriteLine(string.Format("Dictionary添加:{0}個元素,消耗:{1}", 1000000, st.ElapsedTicks));
        }
View Code

Dic速度還是比Hashtable快,但沒有值類型那么明顯,這個測試可能有不准的地方。

2,查找速度測評(兩種情況:值類型和引用類型)

1 值類型

  static void Main(string[] args)
        {
            
           //  GetByString();

            GetByInt();
            
            Console.ReadKey();
        }


        public static void GetByInt()
        {
            //Hashtable
            Hashtable hs = new Hashtable();
            Dictionary<int, int> dic = new Dictionary<int, int>();

            for (int i = 0; i < 10000000; i++)
            {
                hs.Add(i, i);
                dic.Add(i, i);
            }
            long ticks = 0;
            Stopwatch st = new Stopwatch();
            st.Reset();
            for (int i = 0; i < 10; i++)
            {
                st.Start();
                var result = hs[99999+i];
                st.Stop();
                ticks += st.ElapsedTicks;
                st.Reset();
            }
            Console.WriteLine(string.Format("Hashtable查找10次,平均消耗:{0}", (float)ticks / 10));

            //Dictionary
            ticks = 0;
            st.Reset();
            for (int i = 0; i < 10; i++)
            {
                st.Start();
                var result = dic[i];
                st.Stop();
                ticks += st.ElapsedTicks;
                st.Reset();
            }
            Console.WriteLine(string.Format("Dictionary查找10次,平均消耗:{0}", (float)ticks / 10));
        }
View Code

運行結果

2,引用類型

 

 1 static void Main(string[] args)
 2         {
 3            GetByString();
 4             
 5             Console.ReadKey();
 6         }
 7 
 8         public static void GetByString()
 9         {
10             //Hashtable
11             Hashtable hs = new Hashtable();
12             Dictionary<string, string> dic = new Dictionary<string, string>();
13 
14             for (int i = 0; i < 1000000; i++)
15             {
16                 hs.Add(i.ToString(), i.ToString());
17                 dic.Add(i.ToString(), i.ToString());
18             }
19             long ticks = 0;
20             Stopwatch st = new Stopwatch();
21             st.Reset();
22             string key = "9999";
23             for (int i = 0; i < 10; i++)
24             {
25                 st.Start();
26                 var result = hs[key];
27                 st.Stop();
28                 ticks += st.ElapsedTicks;
29                 st.Reset();
30             }
31             Console.WriteLine(string.Format("Hashtable查找10次,平均消耗:{0}", (float)ticks / 10));
32 
33             //Dictionary
34             ticks = 0;
35             st.Reset();
36             for (int i = 0; i < 10; i++)
37             {
38                 st.Start();
39                 var result = dic[key];
40                 st.Stop();
41                 ticks += st.ElapsedTicks;
42                 st.Reset();
43             }
44             Console.WriteLine(string.Format("Dictionary查找10次,平均消耗:{0}", (float)ticks / 10));
45         }
View Code

運行結果

 

 根據上面實驗結果可以得出:

 a,值類型,Hashtable和Dictionary性能相差不大,Hashtable稍微快於Dictionary.

 b,引用類型:Hashtable速度明顯快於Dictionary。

 

 PS:以上是個人不成熟觀點,如果錯誤請各位指出,謝謝,下篇介紹 SortedList 集合。

    另:公司最近招聘.net和java程序員若干,如各位有找工作打算的,請發簡歷到wangjun@tonglukuaijian.com。

  公司在上海閔行浦江智谷。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM