今天在写代码的时候要对数据进行去重,正打算使用Distinct方法的时候,发现这个用了这么久的东西,竟然不知道它是怎么实现的,于是就有了这篇文章.
使用的.net core2.0

1.需求

假如我们有这样一个类

    public class Model{public int Code { get; set; }public int No { get; set; }public override string ToString(){return "No:" + No + ",Code:" + Code;}}

还有这样一组数据

        public static IEnumerable<Model> GetList(){return new List<Model>(){new Model(){No = 1,Code = 1},new Model(){No = 1,Code = 2},new Model(){No = 7,Code = 1},new Model(){No = 11,Code = 1},new Model(){No = 55,Code = 1},new Model(){No = 11,Code = 1},//重复new Model(){No = 6,Code = 7},new Model(){No = 1,Code = 1},new Model(){No = 6,Code = 7},//重复};}

我们要把集合中重复的数据去掉,对的就这么简单个需求,工作中可不会有这么简单的需求.

2.在刚学编程的时候我们可能这样写的

在很久以前一直使用这种简单粗暴的方法解决重复问题

        /// <summary>/// 双重循环去重/// </summary>/// <param name="list"></param>/// <returns></returns>public static IEnumerable<Model> MyDistinct(IEnumerable<Model> list){var result = new List<Model>();foreach (var item in list){//标记var flag = true;foreach (var item2 in result){//已经存在的标记为falseif (item2.Code == item.Code && item2.No == item.No){flag = false;}}if (flag){result.Add(item);}}return result;}

3.后来认识了Distinct

后来知道了Distinct去重,我们写法变成了这样

   /// <summary>/// 比较器/// </summary>public class ModelEquality : IEqualityComparer<Model>{public bool Equals(Model x, Model y){return x.No == y.No && x.Code == y.Code;}public int GetHashCode(Model obj){return obj.No.GetHashCode() + obj.Code.GetHashCode();}}
//这样就可以得到去重后的集合
GetList().Distinct(new ModelEquality());

4.探究Distinct源码

我们去github找一下源码,微软开源的仓库地址:https://github.com/dotnet/corefx
为了篇幅我删掉了一些不相关的一些代码

namespace System.Linq
{public static partial class Enumerable{public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source) => Distinct(source, null);public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer){if (source == null){throw Error.ArgumentNull(nameof(source));}return new DistinctIterator<TSource>(source, comparer);}private sealed class DistinctIterator<TSource> : Iterator<TSource>, IIListProvider<TSource>{private readonly IEnumerable<TSource> _source;private readonly IEqualityComparer<TSource> _comparer;private Set<TSource> _set;private IEnumerator<TSource> _enumerator;public DistinctIterator(IEnumerable<TSource> source, IEqualityComparer<TSource> comparer){_source = source;_comparer = comparer;}public override bool MoveNext(){switch (_state){case 1:_enumerator = _source.GetEnumerator();if (!_enumerator.MoveNext()){Dispose();return false;}TSource element = _enumerator.Current;_set = new Set<TSource>(_comparer);_set.Add(element);_current = element;_state = 2;return true;case 2:while (_enumerator.MoveNext()){element = _enumerator.Current;if (_set.Add(element)){_current = element;return true;}}break;}Dispose();return false;}public override void Dispose(){//省略...}}}
}

Iterator<TSource>是一个抽象类实现了Iterator<TSource> : IEnumerable<TSource>, IEnumerator<TSource>
我们主要看DistinctIterator类中的代码,发现有这么一个私有成员Set<TSource> _set;,我们再看MoveNext方法中有这么一段

                            element = _enumerator.Current;if (_set.Add(element)){_current = element;return true;}

到这里我似乎明白了什么,回忆下Set集合的特点"无序","不可重复",再看代码中只有对set Add成功才对_current赋值,return true.那么这个Set应该就是内部维护的一个集合,也就是我们要的去重后的数据,那么Set里的Add方法就是关键
同样去掉了一些没有用到的,加了注释

namespace System.Linq
{/// <summary>/// A lightweight hash set.///一个 轻量级hash set/// </summary>/// <typeparam name="TElement">The type of the set's items.</typeparam>internal sealed class Set<TElement>{/// <summary>/// The comparer used to hash and compare items in the set./// </summary>private readonly IEqualityComparer<TElement> _comparer;/// <summary>/// The hash buckets, which are used to index into the slots./// hash环,每一个指向了下面Slot中的index/// </summary>private int[] _buckets;/// <summary>/// The slots, each of which store an item and its hash code./// 数组的每一个储存了他们自身和自己的hash/// </summary>private Slot[] _slots;/// <summary>/// The number of items in this set./// </summary>private int _count;/// <summary>/// Constructs a set that compares items with the specified comparer./// </summary>/// <param name="comparer">/// The comparer. If this is <c>null</c>, it defaults to <see cref="EqualityComparer{TElement}.Default"/>./// </param>public Set(IEqualityComparer<TElement> comparer){_comparer = comparer ?? EqualityComparer<TElement>.Default;//初始化长度7_buckets = new int[7];//初始化长度7_slots = new Slot[7];}/// <summary>/// Attempts to add an item to this set./// 我们要看的方法/// </summary>/// <param name="value">The item to add.</param>/// <returns>/// <c>true</c> if the item was not in the set; otherwise, <c>false</c>./// </returns>public bool Add(TElement value){//取的当前项的hashint hashCode = InternalGetHashCode(value);//重复的hashCode的话,  _buckets[hashCode % _buckets.Length] - 1的值就不会是-1//就会进入下面的if判断//for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i]._next){//如果存在重复就会直接返回false,没有的话i会变为_next所指向的hash相等的元素,减少了循环次数,类似链表if (_slots[i]._hashCode == hashCode && _comparer.Equals(_slots[i]._value, value)){return false;}}//Slot数量满了后if (_count == _slots.Length){//对数组进行扩容Resize();}//元素要添加进_slots的下标位置int index = _count;//对数量进行增加_count++;//对当前项的hash 取余int bucket = hashCode % _buckets.Length;//赋值_slots[index]._hashCode = hashCode;_slots[index]._value = value;//当hash第一次出现的时候值为-1,重复出现的时候为上一个出现重复bucket值存放在slots中的索引,-1是因为下一行+1了_slots[index]._next = _buckets[bucket] - 1;//指向当前元素索引+1 出现重复的bucket值则会覆盖旧的bucket位置的值_buckets[bucket] = index + 1;return true;}/// <summary>/// Expands the capacity of this set to double the current capacity, plus one./// 对set扩容/// </summary>private void Resize(){int newSize = checked((_count * 2) + 1);int[] newBuckets = new int[newSize];Slot[] newSlots = new Slot[newSize];Array.Copy(_slots, 0, newSlots, 0, _count);for (int i = 0; i < _count; i++){int bucket = newSlots[i]._hashCode % newSize;newSlots[i]._next = newBuckets[bucket] - 1;newBuckets[bucket] = i + 1;}_buckets = newBuckets;_slots = newSlots;}/// <summary>/// The number of items in this set./// </summary>public int Count => _count;/// <summary>/// Gets the hash code of the provided value with its sign bit zeroed out, so that modulo has a positive result./// </summary>/// <param name="value">The value to hash.</param>/// <returns>The lower 31 bits of the value's hash code.</returns>private int InternalGetHashCode(TElement value) => value == null ? 0 : _comparer.GetHashCode(value) & 0x7FFFFFFF;/// <summary>/// An entry in the hash set./// </summary>private struct Slot{/// <summary>/// The hash code of the item./// hash值/// </summary>internal int _hashCode;/// <summary>/// In the case of a hash collision, the index of the next slot to probe./// 下一个用于检查的元素index/// </summary>internal int _next;/// <summary>/// The item held by this slot./// </summary>internal TElement _value;}}
}

5.分析下去重的思路

图用自带画图画的,难看还请见谅.

我后面回放代码,一步一步调试可能会更容易理解.
1.假如我们第一个Model进行hash取余得到的为0,此时_buckets[0]为0,所以不会进入for循环条件,直接进行下面的赋值操作

_slots[0]=当前的元素 next=-1 hash=7
buckets[0]=1 指向当前元素索引+1

2.继续下一个Model进行hash取余,假如又为0,buckets[0]-1为0,满足循环条件,进入判断,取到_slots[0]的值,进行比较,发现相等的话则会直接返回.
3.继续上面的步骤,这次hash取余为3,没出现过,

_slots[1]=当前的元素 next=-1 hash=10
buckets[2]=2 指向当前元素索引+1

.........
4.这个时候又出现了一次hash取余为3,进入判断中,取到_slots[1]的值,进行比较发现不相等,next为-1不会有下一次循环,

_slots[3]=当前的元素 next=1 hash=10
buckets[2]=4 指向当前元素索引+1

注意此时next不是-1了,而是1,也就是上一个相同hash取余的元素在_slots中的位置,此时形成了一个链表.这样少了很多的比较次数.
5.这个时候又出现了一个hash取余为3的,进入判断中,取到_slots[3]的值,进行比较发现不相等,next为1,则再次与_slots[1]的元素进行比较,如果发现相等的舍弃,反之最后加入到set中
假如不相同,则:

_slots[4]=当前的元素 next=3 hash=10
buckets[2]=5 指向当前元素索引+1

6.结束

结束了,我们发现Distinct使用了hash进行去重,实现思路上感觉很值得我学习(我是写不出来的..).
Distinct很依赖于比较器的GetHashCode方法,如果随便返回一个固定值的话,会增加很大的开销.不要为了偷懒再返回一个固定int值了.
希望这篇文章可以对大家有帮助 有启发

代码地址:https://git.coding.net/changyell/DistinctDemo.git

本人是个菜鸟,文章如果有错误的地方,烦请大佬们指正,谢谢...

转载于:https://www.cnblogs.com/wtbtbd/p/9114589.html

从对集合数据去重到Distinct源码分析相关推荐

  1. 【集合框架】JDK1.8源码分析之HashMap(一)

    转载自  [集合框架]JDK1.8源码分析之HashMap(一) 一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大 ...

  2. YOLO3 数据处理与数据加载 Keras源码分析

    YOLO3 Keras 源码:https://github.com/qqwweee/keras-yolo3 前言 本文从主要是从源码层面对 YOLO3 的数据处理相关内容进行分析与讲解.通常,一个功能 ...

  3. 【集合框架】JDK1.8源码分析之IdentityHashMap(四)

    一.前言 前面已经分析了HashMap与LinkedHashMap,现在我们来分析不太常用的IdentityHashMap,从它的名字上也可以看出来用于表示唯一的HashMap,仔细分析了其源码,发现 ...

  4. Java集合框架之接口Collection源码分析

    本文我们主要学习Java集合框架的根接口Collection,通过本文我们可以进一步了解Collection的属性及提供的方法.在介绍Collection接口之前我们不得不先学习一下Iterable, ...

  5. 【集合框架】JDK1.8源码分析HashSet LinkedHashSet(八)

    一.前言 分析完了List的两个主要类之后,我们来分析Set接口下的类,HashSet和LinkedHashSet,其实,在分析完HashMap与LinkedHashMap之后,再来分析HashSet ...

  6. Distinct源码分析

    以前比较两个List数据,筛选出所需要的数据时候,一直套两层for循环来执行.用到去重(Distinct)的时候,这两个需求其实都是一样的,都是需要比较两个集合,查看了下它的源码,里面确实有值得借鉴的 ...

  7. 3.Java集合-HashSet实现原理及源码分析

    一.HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持,它不保证set的迭代顺序很久不变.此类允许使用null元素 二.HashSet的实现: 对于Ha ...

  8. C# list集合 重复元素 索引_源码分析专题10-纯手写JDK不同版本下的List接口

    纯手写集合框架(下一篇:LinkeList原理) 集合框架介绍 说明:对于以上的框架图有如下几点说明 1.所有集合类都位于java.util包下.Java的集合类主要由两个接口派生而出:Collect ...

  9. 大数据_MapperReduce_Hbase_批处理batchMutate源码分析_数据的写入流程源码分析---Hbase工作笔记0032

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 我们知道hbase是用java语言编写的,所以我们再来看一下源码: 然后我们再去看一下这个batc ...

最新文章

  1. mysql构架,索引,视图,查询语句
  2. 修改vs17中的cordova模板
  3. PAT甲题题解-1050. String Subtraction (20)-水题
  4. MaxCompute(ODPS)上处理非结构化数据的Best Practice
  5. 39. 组合总和020(思路+详解)
  6. 课堂笔记——计算机体系结构(1)
  7. 1.5 编程基础之循环控制 45 金币 方法二(python3实现)
  8. Oralce的内存结构
  9. jQuery.unique引发一个血案
  10. 如何让你产品的用户拥有一流的上传体验
  11. RuntimeError: Legacy autograd function with non-static forward method is deprecated. Please use new-
  12. xmpp 即时通讯
  13. 某度起诉“文库下载神器”,索赔300万
  14. 系统的稳定性判定matlab,matlab分析系统稳定性的方法.doc
  15. 兼容exe的linux系统,国产操作系统下想运行EXE?你何不虚拟个电脑系统出来
  16. 机器学习-各种距离的定义
  17. SEO(搜索引擎优化)是不是任何行业、产品都适合做SEO?
  18. 王宝强离婚成了谁的狂欢|严肃解读数据背后的媒体传播路径
  19. 制作企业网站一般要花多少钱
  20. CH455G驱动数码管

热门文章

  1. MVC教程第四篇:传递表单数据
  2. Datalist Repeater分页
  3. Asp.Net ListView 控件的使用
  4. 深度学习《BiGAN》
  5. 深度学习《CNN架构续篇 - 学习率衰减》
  6. 漫步最优化二十二——收敛速率
  7. C++中lib和dll解析
  8. 软硬件协同漏洞防护技术【会议】
  9. Python3.6 deep learning first step
  10. 【安装教程】windows8.1 下安装ubuntu14.04 双系统