C#数据结构-Dictionary实现

在看过一篇大神写的《带你看懂Dictionary的内部实现》,对Dictionary的内部实现有了一个清晰的了解,但纸上得来终觉浅,作为程序员,还是自己调试一下代码,记忆更深刻,为此专门拷贝出C#的Dictionary源码,因为源码有很多保护级别的代码,不能直接运行。下面贴出我调试没有问题的Dictionary源码(有部分代码没有重写),并附带官方调用实例,方便断点调试。

Dictionary源码:

using System;
using System.Collections;
using System.Collections.Generic;

namespace StructScript
{
    /// <summary>
    /// 哈希表的查找算法主要分为两步:
    /// 第一步是用哈希函数将键转换为数组的一个索引,理想情况下不同的键都能转换为不同的索引值,但是实际上会有多个键哈希到到相同索引值上。
    /// 因此,第二步就是处理碰撞冲突的过程。这里有两种处理碰撞冲突的方法:separate chaining(拉链法)和linear probing(线性探测法)。
    /// </summary>

    public class DictionaryScript<TKey, TValue> : IDictionary<TKey, TValue>
    {
        protected struct Entry
        {
            public int hashCode;    //31位散列值,32最高位表示符号位,-1表示未使用
            public int next;        //下一项的索引值,-1表示结尾
            public TKey key;        //键
            public TValue value;    //值
        }

        protected int[] buckets;//处理hash碰撞,储存由键转换成的数组索引
        protected Entry[] entries;//元素数组,用于维护哈希表中的数据
        protected int count;//元素数量
        protected int freeList;//空闲的列表
        protected int freeCount;//空闲列表元素数量
        protected IEqualityComparer<TKey> comparer;//哈希表中的比较函数
        protected KeyCollection keys;//键集合
        protected ValueCollection values;//值集合
        protected const int MaxPrimeArrayLength = 0x7FEFFFFD;
        //预设素数数组
        protected static readonly int[] primes = {
            3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
            1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
            17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
            187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
            1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369};

        //调用本身的构造方法
        public DictionaryScript() : this(0, null) { }

        public DictionaryScript(int capacity) : this(capacity, null) { }

        public DictionaryScript(int capacity, IEqualityComparer<TKey> comparer)
        {
            if (capacity < 0)
            {
                throw new ArgumentNullException();
            }
            if (capacity > 0)
            {
                Initialize(capacity);
            }
            this.comparer = comparer == null ? EqualityComparer<TKey>.Default : comparer;
        }

        /// <summary>
        /// 需要一个大小为M的数组来储存键值对,那么需要一个能够将任意键转为该数组范围内的索引(0到M-1)的哈希函数
        /// 这个哈希函数应该易于计算并且能够均匀分布所有的键。即对于任意键,0到M-1之间的每个整数都有相等可能性与之对应
        /// 除留余数法。这个方法选择大小为素数M的数组,对于任意正整数k,计算k除以M的余数
        /// 如果M不是素数的话,将不能有效利用键中所包含的所有信息,导致算法不能均匀地分布所有键。
        /// </summary>
        private void Initialize(int capacity)
        {
            //根据构造函数设定的初始容量,获取一个大于并接近的素数
            int size = GetPrime(capacity);
            buckets = new int[size];
            for (int i = 0; i < buckets.Length; i++)
            {
                buckets[i] = -1;
            }
            entries = new Entry[size];
            freeList = -1;
        }

        /// <summary>
        /// 根据构造函数设定的初始容量,获取一个大于并接近的素数
        /// </summary>
        public int GetPrime(int min)
        {
            if (min < 0)
                throw new ArgumentException();

            for (int i = 0; i < primes.Length; i++)
            {
                int prime = primes[i];
                if (prime >= min) return prime;
            }

            //如果超出预先的数组
            for (int i = (min | 1); i < Int32.MaxValue; i += 2)
            {
                if (IsPrime(i) && ((i - 1) % 101 != 0))
                    return i;
            }
            return min;
        }

        /// <summary>
        /// 是否是素数
        /// </summary>
        private bool IsPrime(int candidate)
        {
            if ((candidate & 1) != 0)
            {
                int limit = (int)Math.Sqrt(candidate);
                for (int divisor = 3; divisor <= limit; divisor += 2)
                {
                    if ((candidate % divisor) == 0)
                        return false;
                }
                return true;
            }
            return (candidate == 2);
        }

        /// <summary>
        /// 扩容
        /// </summary>
        private int ExpandPrime(int oldSize)
        {
            int newSize = 2 * oldSize;
            if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize)
            {
                return MaxPrimeArrayLength;
            }
            return GetPrime(newSize);
        }

        public TValue this[TKey key]
        {
            get
            {
                int i = FindEntry(key);
                if (i >= 0)
                {
                    return entries[i].value;
                }
                else
                {
                    throw new KeyNotFoundException();
                }
            }
            set
            {
                Insert(key, value, false);
            }
        }

        public int Count
        {
            get
            {
                return count;
            }
        }

        public KeyCollection Keys
        {
            get
            {
                if (keys == null) keys = new KeyCollection(this);
                return keys;
            }
        }

        public ValueCollection Values
        {
            get
            {
                if (values == null) values = new ValueCollection(this);
                return values;
            }
        }

        IEnumerable<TKey> IDictionary<TKey, TValue>.Keys
        {
            get
            {
                if (keys == null) keys = new KeyCollection(this);
                return keys;
            }
        }

        IEnumerable<TValue> IDictionary<TKey, TValue>.Values
        {
            get
            {
                if (values == null) values = new ValueCollection(this);
                return values;
            }
        }

        public void Add(KeyValuePair<TKey, TValue> item)
        {
            Add(item.Key, item.Value);
        }

        public void Add(TKey key, TValue value)
        {
            Insert(key, value, true);
        }

        private void Insert(TKey key, TValue value, bool add)
        {
            //key不能为空,value可以为空
            if (key == null)
            {
                throw new ArgumentNullException();
            }
            if (buckets == null)
            {
                Initialize(0);
            }
            int collisionCount = 0;
            int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
            //将HashCode的返回值转化为数组索引
            int bucketIndex = hashCode % buckets.Length;
            // 处理hash碰撞冲突
            // 如果转换出的bucketIndex大于等于0,判断buckets数组中有没有相等的,如果相等,需要处理冲突
            for (int i = buckets[bucketIndex]; i >= 0; i = entries[i].next)
            {
                //如果转换的hash值与之前已经添加的hash值相等,同时插入的key与之前的相同,处理冲突,key是唯一的,不能重复
                if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
                {
                    if (add)
                    {
                        throw new ArgumentException();
                    }
                    entries[i].value = value;
                    return;
                }
                collisionCount++;
            }
            //数组索引
            int index;
            //如果空链表的长度大于0,FreeList链表不为空,因此字典会优先把新增元素添加到FreeList链表所指向的位置,添加后FreeCount减1
            if (freeCount > 0)
            {
                index = freeList;
                freeList = entries[index].next;
                freeCount--;
            }
            else
            {
                //如果数组已满,需扩容
                if (count == entries.Length)
                {
                    Resize();
                    bucketIndex = hashCode % buckets.Length;
                }
                index = count;
                count++;
            }
            entries[index].hashCode = hashCode;
            //新增元素的next指向上一个元素的索引
            entries[index].next = buckets[bucketIndex];
            entries[index].key = key;
            entries[index].value = value;
            //记录新增元素的索引
            buckets[bucketIndex] = index;

            // 冲突数达到了一定的阈值,之后更新Hash值
            if (collisionCount > 100 && IsEqualityComparer(comparer))
            {
                comparer = EqualityComparer<TKey>.Default;
                Resize(entries.Length, true);
            }
        }

        private bool IsEqualityComparer(object comparer)
        {
            return (comparer == null || comparer == EqualityComparer<string>.Default);
        }

        /// <summary>
        /// 扩容数组
        /// </summary>
        private void Resize()
        {
            Resize(ExpandPrime(count), false);
        }

        private void Resize(int newSize, bool forceNewHashCodes)
        {
            int[] newBuckets = new int[newSize];
            for (int i = 0; i < newBuckets.Length; i++)
            {
                newBuckets[i] = -1;
            }
            Entry[] newEntries = new Entry[newSize];
            Array.Copy(entries, 0, newEntries, 0, count);
            if (forceNewHashCodes)
            {
                for (int i = 0; i < count; i++)
                {
                    if (newEntries[i].hashCode != -1)
                    {
                        newEntries[i].hashCode = (comparer.GetHashCode(newEntries[i].key) & 0x7FFFFFFF);
                    }
                }
            }
            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;
        }

        public void Clear()
        {
            if (count > 0)
            {
                for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
                Array.Clear(entries, 0, count);
                freeList = -1;
                count = 0;
                freeCount = 0;
            }
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return ContainsKey(item.Key);
        }

        public bool ContainsKey(TKey key)
        {
            return FindEntry(key) >= 0;
        }

        public bool ContainsValue(TValue value)
        {
            if (value == null)
            {
                for (int i = 0; i < count; i++)
                {
                    if (entries[i].hashCode >= 0 && entries[i].value == null) return true;
                }
            }
            else
            {
                EqualityComparer<TValue> c = EqualityComparer<TValue>.Default;
                for (int i = 0; i < count; i++)
                {
                    if (entries[i].hashCode >= 0 && c.Equals(entries[i].value, value)) return true;
                }
            }
            return false;
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }

        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            return Remove(item.Key);
        }

        public bool Remove(TKey key)
        {
            if (key == null)
            {
                throw new ArgumentNullException();
            }
            if (buckets != null)
            {
                int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
                int bucket = hashCode % buckets.Length;
                int last = -1;
                for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next)
                {
                    if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
                    {
                        //如果key在索引0,直接找到或者遍历到数组索引0找到key
                        if (last < 0)
                        {
                            // 把当前索引的bucket数组中的值重置,设置为-1
                            buckets[bucket] = entries[i].next;
                        }
                        else
                        {
                            //遍历数组时,找到key,把当前删除元素的下一个元素的索引赋值给当前删除元素的上一个元素的next
                            entries[last].next = entries[i].next;
                        }
                        entries[i].hashCode = -1;
                        entries[i].next = freeList;
                        entries[i].key = default(TKey);
                        entries[i].value = default(TValue);
                        freeList = i;
                        freeCount++;
                        return true;
                    }
                }
            }
            return false;
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            int i = FindEntry(key);
            if (i >= 0)
            {
                value = entries[i].value;
                return true;
            }
            value = default(TValue);
            return false;
        }

        private int FindEntry(TKey key)
        {
            if (key == null)
            {
                throw new ArgumentNullException();
            }

            if (buckets != null)
            {
                int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
                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;
        }

        public Enumerator GetEnumerator()
        {
            return new Enumerator(this, Enumerator.KeyValuePair);
        }

        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
        {
            return new Enumerator(this, Enumerator.KeyValuePair);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new Enumerator(this, Enumerator.DictEntry);
        }

        public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
        {
            private DictionaryScript<TKey, TValue> dictionary;
            private int index;
            private KeyValuePair<TKey, TValue> current;
            private int getEnumeratorRetType;

            internal const int DictEntry = 1;
            internal const int KeyValuePair = 2;

            internal Enumerator(DictionaryScript<TKey, TValue> dictionary, int getEnumeratorRetType)
            {
                this.dictionary = dictionary;
                index = 0;
                this.getEnumeratorRetType = getEnumeratorRetType;
                current = new KeyValuePair<TKey, TValue>();
            }

            public bool MoveNext()
            {
                while ((uint)index < (uint)dictionary.count)
                {
                    if (dictionary.entries[index].hashCode >= 0)
                    {
                        current = new KeyValuePair<TKey, TValue>(dictionary.entries[index].key, dictionary.entries[index].value);
                        index++;
                        return true;
                    }
                    index++;
                }

                index = dictionary.count + 1;
                current = new KeyValuePair<TKey, TValue>();
                return false;
            }

            public KeyValuePair<TKey, TValue> Current
            {
                get { return current; }
            }

            public void Dispose()
            {
            }

            object IEnumerator.Current
            {
                get
                {
                    if (getEnumeratorRetType == DictEntry)
                    {
                        return new DictionaryEntry(current.Key, current.Value);
                    }
                    else
                    {
                        return new KeyValuePair<TKey, TValue>(current.Key, current.Value);
                    }
                }
            }

            void IEnumerator.Reset()
            {
                index = 0;
                current = new KeyValuePair<TKey, TValue>();
            }
        }


        public sealed class KeyCollection : ICollection<TKey>
        {
            private DictionaryScript<TKey, TValue> dictionary;

            public KeyCollection(DictionaryScript<TKey, TValue> dictionary)
            {
                if (dictionary == null)
                {
                    throw new ArgumentNullException();
                }
                this.dictionary = dictionary;
            }

            public KeyEnumerator GetEnumerator()
            {
                return new KeyEnumerator(dictionary);
            }

            IEnumerator<TKey> IEnumerable<TKey>.GetEnumerator()
            {
                return new KeyEnumerator(dictionary);
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return new KeyEnumerator(dictionary);
            }

            public int Count
            {
                get { return dictionary.Count; }
            }

            public void Add(TKey item)
            {
                throw new NotSupportedException();
            }

            public void Clear()
            {
                throw new NotSupportedException();
            }

            public bool Contains(TKey item)
            {
                return dictionary.ContainsKey(item);
            }

            public void CopyTo(TKey[] array, int arrayIndex)
            {
                throw new NotSupportedException();
            }

            public bool Remove(TKey item)
            {
                throw new NotSupportedException();
            }
        }

        public struct KeyEnumerator : IEnumerator<TKey>, IEnumerator
        {
            private DictionaryScript<TKey, TValue> dictionary;
            private int index;
            private TKey currentKey;

            internal KeyEnumerator(DictionaryScript<TKey, TValue> dictionary)
            {
                this.dictionary = dictionary;
                index = 0;
                currentKey = default(TKey);
            }

            public void Dispose()
            {
            }

            public bool MoveNext()
            {
                while ((uint)index < (uint)dictionary.count)
                {
                    if (dictionary.entries[index].hashCode >= 0)
                    {
                        currentKey = dictionary.entries[index].key;
                        index++;
                        return true;
                    }
                    index++;
                }

                index = dictionary.count + 1;
                currentKey = default(TKey);
                return false;
            }

            public TKey Current
            {
                get
                {
                    return currentKey;
                }
            }

            Object IEnumerator.Current
            {
                get
                {
                    if (index <= 0 || (index > dictionary.count))
                    {
                        throw new IndexOutOfRangeException();
                    }

                    return currentKey;
                }
            }

            void IEnumerator.Reset()
            {
                index = 0;
                currentKey = default(TKey);
            }
        }


        public sealed class ValueCollection : ICollection<TValue>
        {
            private DictionaryScript<TKey, TValue> dictionary;

            public ValueCollection(DictionaryScript<TKey, TValue> dictionary)
            {
                if (dictionary == null)
                {
                    throw new ArgumentNullException();
                }
                this.dictionary = dictionary;
            }

            public ValueEnumerator GetEnumerator()
            {
                return new ValueEnumerator(dictionary);
            }

            IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator()
            {
                return new ValueEnumerator(dictionary);
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return new ValueEnumerator(dictionary);
            }

            public int Count
            {
                get { return dictionary.Count; }
            }

            public void CopyTo(TValue[] array, int arrayIndex)
            {
                throw new NotSupportedException();
            }

            public void Add(TValue item)
            {
                throw new NotSupportedException();
            }

            public void Clear()
            {
                throw new NotSupportedException();
            }

            public bool Contains(TValue item)
            {
                return dictionary.ContainsValue(item);
            }

            public bool Remove(TValue item)
            {
                throw new NotSupportedException();
            }
        }

        public struct ValueEnumerator : IEnumerator<TValue>, IEnumerator
        {
            private DictionaryScript<TKey, TValue> dictionary;
            private int index;
            private TValue currentValue;

            internal ValueEnumerator(DictionaryScript<TKey, TValue> dictionary)
            {
                this.dictionary = dictionary;
                index = 0;
                currentValue = default(TValue);
            }

            public void Dispose()
            {
            }

            public bool MoveNext()
            {
                while ((uint)index < (uint)dictionary.count)
                {
                    if (dictionary.entries[index].hashCode >= 0)
                    {
                        currentValue = dictionary.entries[index].value;
                        index++;
                        return true;
                    }
                    index++;
                }
                index = dictionary.count + 1;
                currentValue = default(TValue);
                return false;
            }

            public TValue Current
            {
                get
                {
                    return currentValue;
                }
            }

            Object IEnumerator.Current
            {
                get
                {
                    if (index <= 0 || (index > dictionary.count))
                    {
                        throw new IndexOutOfRangeException();
                    }

                    return currentValue;
                }
            }

            void IEnumerator.Reset()
            {
                index = 0;
                currentValue = default(TValue);
            }
        }
    }
}

Dictionary官方调用实例:

using System;
using System.Collections.Generic;
using System.Collections;

namespace StructScript
{
    public class TestDictionary
    {
        static void Main(string[] args)
        {
            DictionaryScript<int, string> testDic = new DictionaryScript<int, string>(6);
            testDic.Add(4, "4");
            //在容量为6的情况下,传入4和11的key,得到的hashcode都是一样的,这里就要处理hash碰撞的问题
            testDic.Add(11, "11");

            DictionaryScript<int, string> test1Dic = new DictionaryScript<int, string>(6);
            test1Dic.Add(4, "4");
            test1Dic.Add(10, "11");
            test1Dic.Add(9, "11");
            test1Dic.Add(8, "11");
            test1Dic.Add(7, "11");
            test1Dic.Add(6, "11");
            test1Dic.Add(5, "11");
            //根据构造函数设定的初始容量,获取一个大于并接近的素数7
            //C#内部有一个素数数组,在DictionaryScript的初始化函数Initialize中有具体实现
            //超出数组容量,需要扩容,这里有数组扩容操作
            test1Dic.Add(3, "11");
            string value1;
            test1Dic.TryGetValue(2, out value1);
            test1Dic.Remove(3);


            //下面是官方调用实例
            DictionaryScript<string, string> openWith = new DictionaryScript<string, string>();

            openWith.Add("txt", "notepad.exe");
            openWith.Add("bmp", "paint.exe");
            openWith.Add("dib", "paint.exe");
            openWith.Add("rtf", "wordpad.exe");

            // The Add method throws an exception if the new key is 
            // already in the dictionary.
            try
            {
                openWith.Add("txt", "winword.exe");
            }
            catch (ArgumentException)
            {
                Console.WriteLine("An element with Key = \"txt\" already exists.");
            }

            // The Item property is another name for the indexer, so you 
            // can omit its name when accessing elements. 
            Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]);

            // The indexer can be used to change the value associated
            // with a key.
            openWith["rtf"] = "winword.exe";
            Console.WriteLine("For key = \"rtf\", value = {0}.",
                openWith["rtf"]);

            // If a key does not exist, setting the indexer for that key
            // adds a new key/value pair.
            openWith["doc"] = "winword.exe";

            // The indexer throws an exception if the requested key is
            // not in the dictionary.
            try
            {
                Console.WriteLine("For key = \"tif\", value = {0}.",
                    openWith["tif"]);
            }
            catch (KeyNotFoundException)
            {
                Console.WriteLine("Key = \"tif\" is not found.");
            }

            // When a program often has to try keys that turn out not to
            // be in the dictionary, TryGetValue can be a more efficient 
            // way to retrieve values.
            string value = "";
            if (openWith.TryGetValue("tif", out value))
            {
                Console.WriteLine("For key = \"tif\", value = {0}.", value);
            }
            else
            {
                Console.WriteLine("Key = \"tif\" is not found.");
            }

            // ContainsKey can be used to test keys before inserting 
            // them.
            if (!openWith.ContainsKey("ht"))
            {
                openWith.Add("ht", "hypertrm.exe");
                Console.WriteLine("Value added for key = \"ht\": {0}",
                    openWith["ht"]);
            }

            // When you use foreach to enumerate dictionary elements,
            // the elements are retrieved as KeyValuePair objects.
            Console.WriteLine();
            foreach (KeyValuePair<string, string> kvp in openWith)
            {
                Console.WriteLine("Key = {0}, Value = {1}",
                    kvp.Key, kvp.Value);
            }

            // To get the values alone, use the Values property.
            DictionaryScript<string, string>.ValueCollection valueColl =
                openWith.Values;

            // The elements of the ValueCollection are strongly typed
            // with the type that was specified for dictionary values.
            Console.WriteLine();
            foreach (string s in valueColl)
            {
                Console.WriteLine("Value = {0}", s);
            }

            // To get the keys alone, use the Keys property.
            DictionaryScript<string, string>.KeyCollection keyColl =
                openWith.Keys;

            // The elements of the KeyCollection are strongly typed
            // with the type that was specified for dictionary keys.
            Console.WriteLine();
            foreach (string s in keyColl)
            {
                Console.WriteLine("Key = {0}", s);
            }

            // Use the Remove method to remove a key/value pair.
            Console.WriteLine("\nRemove(\"doc\")");
            openWith.Remove("doc");

            if (!openWith.ContainsKey("doc"))
            {
                Console.WriteLine("Key \"doc\" is not found.");
            }


            Console.ReadLine();
        }
    }
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值