Distinct源码分析
以前比較兩個(gè)List數(shù)據(jù),篩選出所需要的數(shù)據(jù)時(shí)候,一直套兩層for循環(huán)來執(zhí)行。用到去重(Distinct)的時(shí)候,這兩個(gè)需求其實(shí)都是一樣的,都是需要比較兩個(gè)集合,查看了下它的源碼,里面確實(shí)有值得借鑒的地方。
先附上源碼一直我調(diào)試的代碼,大部分地方加上了注釋
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //初始化集合 6 List<Point> points = new List<Point>() 7 { 8 new Point() {X=1,Y=2}, 9 new Point() {X=7,Y=2}, 10 new Point() {X=2,Y=2}, 11 new Point() {X=2,Y=3}, 12 new Point() {X=3,Y=2}, 13 new Point() {X=4,Y=2}, 14 new Point() {X=5,Y=2}, 15 new Point() {X=6,Y=3}, 16 new Point() {X=2,Y=3} 17 }; 18 var distinctPoints = DistinctIterator(points, new PointCompare()).ToList(); 19 Console.Read(); 20 } 21 22 //調(diào)用Distinct 方法 內(nèi)部調(diào)的這個(gè)方法 抽取出來了 23 static IEnumerable<TSource> DistinctIterator<TSource>(IEnumerable<TSource> source, IEqualityComparer<TSource> comparer) 24 { 25 Set<TSource> set = new Set<TSource>(comparer); 26 foreach (TSource element in source) 27 if (set.Add(element)) 28 //若返回true則添加到集合中 29 yield return element; 30 } 31 } 32 33 struct Point 34 { 35 public int X { get; set; } 36 37 public int Y { get; set; } 38 } 39 40 class PointCompare : IEqualityComparer<Point> 41 { 42 43 44 public bool Equals(Point x, Point y) 45 { 46 return x.X == y.X && x.Y == y.Y; 47 //return false; 48 } 49 50 public int GetHashCode(Point obj) 51 { 52 // return 1; 53 return obj.X.GetHashCode() ^ obj.Y.GetHashCode(); 54 } 55 } 56 57 internal class Set<TElement> 58 { 59 int[] buckets; 60 Slot[] slots; 61 int count; 62 int freeList; 63 IEqualityComparer<TElement> comparer; 64 65 public Set() : this(null) { } 66 67 //初始化 68 public Set(IEqualityComparer<TElement> comparer) 69 { 70 //若comparer為null則string、int...基元類型 71 if (comparer == null) comparer = EqualityComparer<TElement>.Default; 72 this.comparer = comparer; 73 //大小為7 74 buckets = new int[7]; 75 //大小為7 76 slots = new Slot[7]; 77 freeList = -1; 78 } 79 80 // If value is not in set, add it and return true; otherwise return false 81 public bool Add(TElement value) 82 { 83 //若Find返回true則代表已重復(fù),則不會(huì)添加到集合中 84 return !Find(value, true); 85 } 86 87 // Check whether value is in set 88 //沒用到 89 public bool Contains(TElement value) 90 { 91 return Find(value, false); 92 } 93 94 // If value is in set, remove it and return true; otherwise return false 95 //沒用到 96 public bool Remove(TElement value) 97 { 98 int hashCode = InternalGetHashCode(value); 99 int bucket = hashCode % buckets.Length; 100 int last = -1; 101 for (int i = buckets[bucket] - 1; i >= 0; last = i, i = slots[i].next) 102 { 103 if (slots[i].hashCode == hashCode && comparer.Equals(slots[i].value, value)) 104 { 105 if (last < 0) 106 { 107 buckets[bucket] = slots[i].next + 1; 108 } 109 else 110 { 111 slots[last].next = slots[i].next; 112 } 113 slots[i].hashCode = -1; 114 slots[i].value = default(TElement); 115 slots[i].next = freeList; 116 freeList = i; 117 return true; 118 } 119 } 120 return false; 121 } 122 123 bool Find(TElement value, bool add) 124 { 125 //調(diào)用comparer的GetHashCode 126 int hashCode = InternalGetHashCode(value); 127 //根據(jù)hash值取余運(yùn)算 查找相同結(jié)果的數(shù)據(jù) 比較hash值以及調(diào)用IEqualityComparer.Equals方法,若相同 則代表存在相同數(shù)據(jù) 128 for (int i = buckets[hashCode % buckets.Length] - 1; i >= 0; i = slots[i].next) 129 { 130 if (slots[i].hashCode == hashCode && comparer.Equals(slots[i].value, value)) return true; 131 } 132 if (add) 133 { 134 int index; 135 //沒用到。。。 136 if (freeList >= 0) 137 { 138 index = freeList; 139 freeList = slots[index].next; 140 } 141 else 142 { 143 //當(dāng)循環(huán)數(shù)量大于slots.Length值(初始7)時(shí)則會(huì)擴(kuò)充slots,buckets的數(shù)量,以及重置對(duì)應(yīng)hash值的取余 144 if (count == slots.Length) Resize(); 145 index = count; 146 count++; 147 } 148 //hash取余 149 int bucket = hashCode % buckets.Length; 150 //設(shè)置hash值 151 slots[index].hashCode = hashCode; 152 //指向當(dāng)前的元素 153 slots[index].value = value; 154 //指向相同hash取余的上一個(gè)元素索引* 有了這個(gè)才能夠在判斷是否重復(fù)的地方減少比較量 155 slots[index].next = buckets[bucket] - 1; 156 //指向最近的相同元素 157 buckets[bucket] = index + 1; 158 } 159 return false; 160 } 161 162 void Resize() 163 { 164 //不理解要+1 保證奇數(shù)? 165 int newSize = checked(count * 2 + 1); 166 int[] newBuckets = new int[newSize]; 167 Slot[] newSlots = new Slot[newSize]; 168 Array.Copy(slots, 0, newSlots, 0, count); 169 for (int i = 0; i < count; i++) 170 { 171 int bucket = newSlots[i].hashCode % newSize; 172 newSlots[i].next = newBuckets[bucket] - 1; 173 newBuckets[bucket] = i + 1; 174 } 175 buckets = newBuckets; 176 slots = newSlots; 177 } 178 179 internal int InternalGetHashCode(TElement value) 180 { 181 //[....] DevDivBugs 171937. work around comparer implementations that throw when passed null 182 return (value == null) ? 0 : comparer.GetHashCode(value) & 0x7FFFFFFF; 183 } 184 185 internal struct Slot 186 { 187 //value的hash值 188 internal int hashCode; 189 //元素 190 internal TElement value; 191 //指向下一個(gè)元素索引 192 internal int next; 193 } 194 }?
著重說說它去重的思路,代碼可以拷出來調(diào)試一步一步走,會(huì)對(duì)下面的更能理解
他用到了hash環(huán)的思路,初始大小是7,圖隨便畫的,丑陋還請(qǐng)海涵
1.第一個(gè)Point元素hash值取余為3
solts[0]=當(dāng)前元素 next=-1 hash=3
buckets[3]=1 指向第一個(gè)元素索引+1 ? ?其實(shí)也是當(dāng)前的count數(shù)
2.第二個(gè)Point元素hash值取余為5
solts[1]=當(dāng)前元素 next=-1 hash=5
buckets[5]=2 指向第二個(gè)元素索引+1
....
3.假設(shè)第三個(gè)Point元素hash值為10,取余為3
會(huì)對(duì)相同取余結(jié)果值的元素比較,目前來說也就是第一步的Point元素 比較對(duì)應(yīng)的hash值以及調(diào)用Equals方法 返回false(上面兩部也會(huì)比較,但因?yàn)闆]有元素所以未強(qiáng)調(diào))
則solts[2]=當(dāng)前元素 next=0(這樣子在第128行進(jìn)行比較的時(shí)候就產(chǎn)生了一個(gè)類似鏈表性質(zhì)) hash=10
buckets[3]=3 指向第三個(gè)元素索引+1
4.假設(shè)第四個(gè)Point元素跟第一個(gè)元素相同 也就是hash也為3?
同樣也會(huì)執(zhí)行第128行 ?根據(jù) buckets[3] 得到對(duì)應(yīng)的第一個(gè)元素為3-1=2 ?取solts[2] 得到第三步的元素,進(jìn)行比較, 很顯然兩個(gè)元素不相同,根據(jù)對(duì)應(yīng)的next也就是0得到第一個(gè)元素solts[0]進(jìn)行比較 ?返回true也就是存在相同元素,則去掉當(dāng)前重復(fù)項(xiàng)。
很顯然你會(huì)發(fā)現(xiàn)第二個(gè)元素沒有進(jìn)行比較,這樣子就會(huì)較少數(shù)據(jù)的比較次數(shù)。
當(dāng)元素越來越多的時(shí)候 144行的時(shí)候就會(huì)執(zhí)行他的Resize方法,會(huì)將大小*2+1(為啥要+1?),對(duì)應(yīng)的hash取余結(jié)果重寫來計(jì)算
你會(huì)發(fā)現(xiàn)Distinct里面對(duì)GetHashCode依賴特別大,我發(fā)現(xiàn)很多人實(shí)現(xiàn)IEqualityComparer<T>接口時(shí)不會(huì)關(guān)注他的GetHashCode方法(只是返回了一個(gè)特定的int值),而只是實(shí)現(xiàn)了里面的Equals方法,通過上面的解釋你會(huì)發(fā)現(xiàn)這樣子其實(shí)比我們用兩次for循環(huán)開銷很大,所以強(qiáng)烈建議重寫他的GetHashCode方法。
強(qiáng)調(diào)一點(diǎn),當(dāng)兩個(gè)元素的相同時(shí),那他的hashcode肯定也是相同的
希望這樣的思路能在大家的coding中有所幫助
轉(zhuǎn)載于:https://www.cnblogs.com/sjr10/p/Distinct.html
總結(jié)
以上是生活随笔為你收集整理的Distinct源码分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Java学习笔记之十五】Java中的s
- 下一篇: 添加分页