今天在写代码的时候要对数据进行去重,正打算使用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)
{
//已经存在的标记为false
if (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)
{
//取的当前项的hash
int 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

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

从对集合数据去重到Distinct源码分析的更多相关文章

  1. Linq聚合操作之Aggregate,Count,Sum,Distinct源码分析

    Linq聚合操作之Aggregate,Count,Sum,Distinct源码分析 一:Linq的聚合运算 1. 常见的聚合运算:Aggregate,Count, Sum, Distinct,Max, ...

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

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

  3. 【集合框架】JDK1.8源码分析之ArrayList详解(一)

    [集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...

  4. vue 快速入门 系列 —— 侦测数据的变化 - [vue 源码分析]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [vue 源码分析] 本文将 vue 中与数据侦测相关的源码摘了出来,配合上文(侦测数据的变化 - [基本实现]) 一起来分析一下 vue ...

  5. 【集合框架】JDK1.8源码分析之ArrayList(六)

    一.前言 分析了Map中主要的类之后,下面我们来分析Collection下面几种常见的类,如ArrayList.LinkedList.HashSet.TreeSet等.下面通过JDK源码来一起分析Ar ...

  6. 【集合框架】JDK1.8源码分析之LinkedList(七)

    一.前言 在分析了ArrayList了之后,紧接着必须要分析它的同胞兄弟:LinkedList,LinkedList与ArrayList在底层的实现上有所不同,其实,只要我们有数据结构的基础,在分析源 ...

  7. Java集合详解及List源码分析

    对于数组我们应该很熟悉,一个数组在内存中总是一块连续的存储空间,数组的创建使用new关键字,数组是引用类型的数据,一旦第一个元素的位置确定,那么后面的元素位置也就确定了,数组有一个最大的局限就是数组一 ...

  8. java集合【13】——— Stack源码分析走一波

    前言 集合源码分析系列:Java集合源码分析 前面已经把Vector,ArrayList,LinkedList分析完了,本来是想开始Map这一块,但是看了下面这个接口设计框架图:整个接口框架关系如下( ...

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

    一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化,其中最重要的一个优化就是桶中的元素不再唯一按照链表组合,也 ...

随机推荐

  1. How to save rules of the iptables?

    The easy way is to use iptables-persistent. Install iptables-persistent: sudo apt-get install iptabl ...

  2. Android Tab与TabHost

    这就是Tab,而盛放Tab的容器就是TabHost 如何实现?? 每一个Tab还对应了一个布局,这个就有点好玩了.一个Activity,对应了多个功能布局. ①新建一个Tab项目,注意,不要生成mai ...

  3. CentOS 7.2mini版本下编译安装php7.0.10+MySQL5.7.14+Nginx1.10.1

    一.安装前的准备工作 1.yum update    #更新系统 1.1)vi /etc/selinux/config #  禁止SELINUX,设置SELINUX=disabled 2.yum in ...

  4. package.json作用

    这个文档的内容是你必须要知道的,它必须是JSON文本格式.每个项目的根目录下面,一般都有一个package.json文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称.版本.许可证等元 ...

  5. Java 8 Date-Time API概览

    更新时间:2018-04-19 根据网上资料整理 java 8增加了新的Date-Time API (JSR 310),增强对日期与时间的处理.它在很大程度上受到Joda-Time的影响.之前写过一篇 ...

  6. [翻译] MagicPie

    MagicPie Powerful pie layer for creating your own pie view. PieLayer provide great animation with si ...

  7. December 25th 2016 Week 53rd Sunday

    Patience is bitter, but its fruit is sweet. 忍耐是痛苦的,但它的果实是甜蜜的. What can we do if there is no fruit of ...

  8. December 15th 2016 Week 51st Thursday

    At the touch of love everyone becomes a poet. 每一个沐浴在爱河中的人都是诗人. You call your love as your sweetheart ...

  9. linux下常用命令:

    常用指令 ls        显示文件或目录 -l           列出文件详细信息l(list) -a          列出当前目录下所有文件及目录,包括隐藏的a(all) mkdir     ...

  10. 结构类型:Struct

    一.概述: 结构类似于类,但结构为值类型,存储于栈中. 结构不能继承和被继承,但可实现接口. 结构成员访问级别有public,private(默认) ,internal. 1.简单结构 可以将无方法, ...