什么时候应该使用HashSet <T>类型?


134

我正在研究这种HashSet<T>类型,但是我不知道它在收藏中的位置。

一个人可以用它代替List<T>吗?我以为a的性能HashSet<T>会更好,但是我看不到对其元素的单独访问。

它仅用于枚举吗?

Answers:


228

重要的事情HashSet<T>就在名称中:它是set。单个集合唯一可以做的就是确定其成员,并检查某项是否为成员。

询问您是否可以检索单个元素(例如set[45])误解了集合的概念。集合中没有第45个元素。集合中的项目没有排序。集{1、2、3}和{2、3、1}在各个方面都是相同的,因为它们具有相同的成员资格,而成员资格就很重要。

对a进行迭代有些危险,HashSet<T>因为这样做会对集合中的项目施加顺序。该顺序实际上不是集合的属性。您不应该依赖它。如果集合中项目的排序对您很重要,则该集合不是集合。

集确实是有限的,并且具有唯一的成员。另一方面,它们真的很快。


1
框架提供SortedSet数据结构的事实要么与您所说的订单不是集合的属性相矛盾,要么指出开发团队的误解。
Veverke '16

10
我认为说HashSet未定义项目的顺序是更正确的,所以不要依赖迭代器的顺序。如果因为对集合中的项目进行了某些操作而对集合进行迭代,那么除非您依赖与订单相关的任何内容,否则并不危险。A 具有加号顺序的所有属性,但是不衍生自; 换句话说,SortedSet是不同对象的有序集合SortedSetHashSet SortedSetHashSet
套件

110

这是我使用的真实示例HashSet<string>

我的UnrealScript文件语法突出显示工具的一部分是一项新功能,可突出显示Doxygen样式的注释。我需要能够知道a @\命令是否有效,以确定是将其显示为灰色(有效)还是红色(无效)。我拥有HashSet<string>所有有效命令中的a,因此,每当我@xxx在词法分析器中命中一个标记时,便将其validCommands.Contains(tokenText)用作O(1)有效性检查。除了有效命令集中是否存在该命令外,我真的不在乎。让我们看看我面临的替代方案:

  • Dictionary<string, ?>:我使用哪种类型的值?该值没有意义,因为我将要使用ContainsKey。注意:在.NET 3.0之前,这是O(1)查找的唯一选择- HashSet<T>为3.0添加并扩展ISet<T>为4.0 实现。
  • List<string>:如果我对列表进行排序,则可以使用BinarySearch,它是O(log n)(没有看到上面提到的这个事实)。但是,由于我的有效命令列表是一个不会更改的固定列表,因此,这比简单地...更合适。
  • string[]:同样,Array.BinarySearch给出O(log n)性能。如果列表很短,那么这可能是性能最好的选择。它总是有空间开销小于HashSetDictionaryList。即使使用BinarySearch,对于大型集合来说也并不快,但是对于小型集合来说,值得尝试。我的有几百个物品,所以我将其传递给了我。

24

一个HashSet<T>实现ICollection<T>接口:

public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
    // Methods
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);

    // Properties
   int Count { get; }
   bool IsReadOnly { get; }
}

一个List<T>工具IList<T>,扩展了ICollection<T>

public interface IList<T> : ICollection<T>
{
    // Methods
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);

    // Properties
    T this[int index] { get; set; }
}

HashSet具有设置的语义,该语义通过内部的哈希表实现:

集合是不包含重复元素且其元素没有特定顺序的集合。

如果HashSet失去索引/位置/列表行为,它将获得什么收益?

从HashSet中添加和检索项目始终是由对象本身进行的,而不是通过索引器进行的,并且接近O(1)操作(列表为O(1)add,O(1)通过索引检索,O(n)查找) /去掉)。

Dictionary<TKey,TValue>通过仅将键添加/删除键作为值,而忽略字典值本身,可以将HashSet的行为与使用a进行比较。您可能希望字典中的键没有重复的值,这就是“设置”部分的重点。


14

性能是选择HashSet而不是List的不好理由。相反,什么能更好地抓住您的意图?如果顺序很重要,则Set(或HashSet)不可用。如果允许重复,同样。但是在很多情况下,当我们不关心订单时,我们宁愿没有重复的商品,那就是您要购买Set的时候。


21
Performance would be a bad reason to choose HashSet over List:我只是不同意你的看法。这就是说,选择Dictionray而不是两个List不会对性能有所帮助。看看以下文章
奥斯卡·梅德罗斯

11
@Oscar:我不是说集合不是更快-我说那将是选择它们的不好依据。如果您要表示有序集合,那么集合根本就行不通,尝试穿刺它是错误的。如果您想要的收藏没有订单,那么一套是完美的-快速。但是重要的是第一个问题:您要代表什么?
卡尔·马纳斯特

2
但是考虑一下。如果你想保持检查给出的字符串是否10,000串的一些集合的成员,在技术上,string[].ContainsHashSet<string>.Contains表达你的意图同样出色; 选择HashSet的原因是它将运行得更快。
Casey 2015年

12

HashSet是通过哈希实现的集合。集合是不包含重复元素的值的集合。集合中的值通常也无序。因此,不能,一个集合不能用于替换列表(除非您首先应该使用一个集合)。

如果您想知道一个集合可能有什么好处:显然,您想摆脱重复的任何地方。举一个人为设计的例子,假设您有一个软件项目的10,000个修订版本的列表,并且想知道有多少人对该项目做出了贡献。您可以使用Set<string>和遍历修订列表,然后将每个修订的作者添加到集合中。一旦完成迭代,集合的大小就是您要寻找的答案。


但是Set不允许检索单个元素吗?喜欢套装[45]?
琼·芬格

2
为此,您将遍历集合中的成员。其他典型操作是检查集合是否包含元素或获取集合的大小。
伯爵

11

HashSet将用于删除IEnumerable集合中的重复元素。例如,

List<string> duplicatedEnumrableStrings = new List<string> {"abc", "ghjr", "abc", "abc", "yre", "obm", "ghir", "qwrt", "abc", "vyeu"};
HashSet<string> uniqueStrings = new HashSet(duplicatedEnumrableStrings);

在运行这些代码之后,uniqueStrings将保存{“ abc”,“ ghjr”,“ yre”,“ obm”,“ qwrt”,“ vyeu”};


6

散列集最常见的用途可能是查看散列集是否包含某个元素,该元素接近于散列集的O(1)操作(假设散列函数足够强大),而不是列表中包含是否为O( n)(以及排序为O(log n)的集合)。因此,如果您进行大量检查,以确定某个项目是否包含在某些列表中,那么HASSET可能会提高性能。如果只迭代它们,则不会有太大差异(整个集合的迭代次数为O(n),与列表和哈希集相同,添加项时开销更大)。

不,您无法索引集,因为集是无序的,所以无论如何这都没有意义。如果添加一些项目,则该集合将不会记住哪个是第一个,然后是第二个,依此类推。


如果仅迭代它们,则HashSet方法与List相比会增加很多内存使用量。
塞缪尔·沃伦(SamuelWarren)2010年

5

HashSet<T>是.NET框架中的数据结构,它能够将数学集表示为对象。在这种情况下,它使用哈希码(GetHashCode每个项目的结果)比较集合元素的相等性。

集合与列表的不同之处在于,它仅允许一次出现包含在其中的相同元素。如果您尝试添加第二个相同的元素,它HashSet<T>只会返回false。确实,查找元素非常快(O(1)时间),因为内部数据结构只是一个哈希表。

如果你想知道使用哪个,请注意,使用List<T>其中HashSet<T>的appropiate是不是最大的错误,虽然它可能会允许你在哪儿集合中的不良重复的项目问题。而且,查找(项目检索)的效率大大提高-理想情况下O(1)(用于完美存储)而不是O(n)时间-在许多情况下这非常重要。


1
将现有项目添加到集合中不会引发异常。添加将仅返回false。另外:从技术上讲,哈希查找是O(n),而不是O(1),除非您具有完善的哈希函数。当然,在实践中,除非哈希函数真的很糟糕,否则您会假设它是O(1)。
sepp2k

1
@ sepp2k:是的,所以它返回一个布尔值……重点是,它通知您。如果存储很糟糕,则哈希查找是最坏的情况 O(n)-通常更接近O(1)。
Noldorin

4

List<T>用于存储有序信息集。如果知道列表元素的相对顺序,则可以在恒定时间内访问它们。但是,要确定元素在列表中的位置或检查元素是否在列表中,查找时间是线性的。另一方面,HashedSet<T>不保证所存储数据的顺序,因此为其元素提供恒定的访问时间。

顾名思义,它HashedSet<T>是一种实现集合语义的数据结构。数据结构经过优化以实现集合操作(​​即,并集,差值,相交),而传统的List实现无法高效地完成此操作。

因此,选择使用哪种数据类型实际上取决于您要对应用程序执行的操作。如果您不关心元素在集合中的排序方式,只想枚举或检查是否存在,请使用HashSet<T>。否则,请考虑使用List<T>或其他合适的数据结构。


2
另一个警告:集合通常只允许一个元素出现一次。
史蒂夫·吉迪

1

简而言之-任何时候您都想使用Dictionary(或Dictionary,其中S是T的属性),则应考虑使用HashSet(或HashSet +在T上实现IEquatable,它等于S)


5
除非您关心密钥,否则应该使用字典。
Hardwareguy 2010年

1

在基本的预期方案中,HashSet<T>当您要对两个集合进行比LINQ提供的更具体的设置操作时,应使用。LINQ方法,如DistinctUnionIntersectExcept在大多数情况下是足够的,但有时你可能需要更细粒度的操作,并HashSet<T>提供:

  • UnionWith
  • IntersectWith
  • ExceptWith
  • SymmetricExceptWith
  • Overlaps
  • IsSubsetOf
  • IsProperSubsetOf
  • IsSupersetOf
  • IsProperSubsetOf
  • SetEquals

LINQ和HashSet<T>“重叠”方法之间的另一个区别是LINQ总是返回new IEnumerable<T>,并且HashSet<T>方法会修改源集合。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.