如何使用 C# 中的 HashSet

2021-03-02 DotNetCore實戰

譯文連結:https://www.infoworld.com/article/3586972/how-to-use-hashset-in-csharp.html

HashSet 是一個優化過的無序集合,提供對元素的高速查找和高性能的set集合操作,而且 HashSet 是在 .NET 3.5 中被引入的,在 System.Collection.Generic 命名空間下,這篇就來討論一下如何使用這個 HashSet。

要運行本篇文章的案例代碼,你需要安裝一下 Visual Studio 2019,如果沒有的話可以到官網下載一下。

使用 VS 創建一個 .NET Core 控制臺程序

首先,我通過 VS2019 創建一個 .NET Core 控制臺程序,創建可以參考下面步驟:

在 Create new project 窗口上,從模板列表中選擇:Console App (.NET Core)

在 Configure your new project 界面填好你的項目名稱和存放路徑

這樣我們就創建好了一個新項目,本文的後面部分就會在這個項目裡來給大家分享 HashSet 的一些必備知識。

HashSet 到底是什麼

所謂的HashSet,指的就是 System.Collections.Generic 命名空間下的 HashSet<T> 類,它是一個高性能,無序的集合,因此HashSet它並不能做排序操作,也不能包含任何重複的元素,Hashset 也不能像數組那樣使用索引,所以在 HashSet 上你無法使用 for 循環,只能使用 foreach 進行迭代,HashSet 通常用在處理元素的唯一性上有著超高的性能。

HashSet<T> 實現了如下幾個接口:


public class HashSet<T> : System.Collections.Generic.ICollection<T>,
System.Collections.Generic.IEnumerable<T>, 
System.Collections.Generic.IReadOnlyCollection<T>,
System.Collections.Generic.ISet<T>,
System.Runtime.Serialization.IDeserializationCallback,
System.Runtime.Serialization.ISerializable
{
}

HashSet 只能包含唯一的元素,它的內部結構也為此做了專門的優化,值得注意的是,HashSet 也可以存放單個的 null 值,可以得出這麼一個結論:如何你想擁有一個具有唯一值的集合,那麼 HashSet 就是你最好的選擇,何況它還具有超高的檢索性能。

從 HashSet 中查找一個元素

如果想判斷某一個元素是否在 HashSet 內,建議使用 Contains 進行判斷,代碼如下:


        static void Main(string[] args)
        {
            HashSet<string> hashSet = new HashSet<string>();
            hashSet.Add("A");
            hashSet.Add("B");
            hashSet.Add("C");
            hashSet.Add("D");
            if (hashSet.Contains("D"))
                Console.WriteLine("The required element is available.");
            else
                Console.WriteLine("The required element isn’t available.");
            Console.ReadKey();
        }

HashSet中的元素唯一性

如果你向 HashSet 中插入重複的元素,它的內部會忽視這次操作而不像別的集合一樣拋出異常,接下來展示一下代碼:


        static void Main(string[] args)
        {
            HashSet<string> hashSet = new HashSet<string>();
            hashSet.Add("A");
            hashSet.Add("B");
            hashSet.Add("C");
            hashSet.Add("D");
            hashSet.Add("D");
            Console.WriteLine("The number of elements is: {0}", hashSet.Count);
            Console.ReadKey();
        }

當你執行了這個程序,輸出結果如下圖:

現在可以考慮一下下面的代碼段,它展示了重複的元素是如何被剔除的。


        static void Main(string[] args)
        {
            string[] cities = new string[] {
                "Delhi",
                "Kolkata",
                "New York",
                "London",
                "Tokyo",
                "Washington",
                "Tokyo"
            };
            HashSet<string> hashSet = new HashSet<string>(cities);
            foreach (var city in hashSet)
            {
                Console.WriteLine(city);
            }
        }

當你執行完上面的程序,重複的城市名稱已經被移除了。

從 HashSet 中移除元素

從HashSet 中刪除某一個元素可以調用 Remove 方法,它的語法結構如下:


public bool Remove (T item);

如果在集合中找到了這個元素,Remove方法將會刪除這個元素並且返回true,否則返回 false。

下面的代碼片段展示了如何使用 Remove 方法刪除 HashSet 中的元素


string item = "D";
if(hashSet.Contains(item))
{
   hashSet.Remove(item);
}

如果你想刪除 HashSet 中的所有元素,可以調用 Clear 方法。

HashSet 的 set操作

HashSet提供了非常多的方法用於 set集合 操作上,比如說:IntersectWith, UnionWith, IsProperSubsetOf, ExceptWith, 和 SymmetricExceptWith

IsProperSubsetOf

這個 IsProperSubsetOf 用於判斷 HashSet 是否為某一個集合的完全子集,可以看下面的例子:


HashSet<string> setA = new HashSet<string>() { "A", "B", "C", "D" };
HashSet<string> setB = new HashSet<string>() { "A", "B", "C", "X" };
HashSet<string> setC = new HashSet<string>() { "A", "B", "C", "D", "E" };
if (setA.IsProperSubsetOf(setC))
   Console.WriteLine("setC contains all elements of setA.");
if (!setA.IsProperSubsetOf(setB))
   Console.WriteLine("setB does not contains all elements of setA.");

如果你執行了上面這個程序,你會在控制臺上看到如下的輸出:

UnionWith

UnionWith方法常用於集合的合併,比如說下面的代碼:


HashSet<string> setA = new HashSet<string>() { "A", "B", "C", "D", "E" };
HashSet<string> setB = new HashSet<string>() { "A", "B", "C", "X", "Y" };
setA.UnionWith(setB);
foreach(string str in setA)
{
   Console.WriteLine(str);
}

當你執行完上面的代碼,SetB 集合會被 SetA 集合吞掉,最後 SetA 集合將會是包括:"A", "B", "C", "D", "E", "X", and "Y" 。

IntersectWith

IntersectWith 方法常用於表示兩個 HashSet 的交集,下面的例子或許會讓你更加理解:


HashSet<string> setA = new HashSet<string>() { "A", "B", "C", "D", "E" };
HashSet<string> setB = new HashSet<string>() { "A", "X", "C", "Y"};
setA.IntersectWith(setB);
foreach (string str in setA)
{
    Console.WriteLine(str);
}

當你運行了上面的這段程序,只有兩個 HashSet 中都存在的元素才會輸出到控制臺中,輸出結果如下所示:

ExceptWith

ExceptWith 方法表示數學上的減法操作,這個時間複雜度是 O(N),假定你有兩個HashSet 集合,分別叫 setA 和 setB,並且用了下面的語句。


setA.ExceptWith(setB);

它返回的元素為:setA中有,setB中沒有 的最終結果,如果還不明白的話,使用如下代碼輔助理解:


HashSet<string> setA = new HashSet<string>() { "A", "B", "C", "D", "E" };
HashSet<string> setB = new HashSet<string>() { "A", "X", "C", "Y" };
setA.ExceptWith(setB);
foreach (string str in setA)
{
   Console.WriteLine(str);
}

當你執行了上面這段程序,元素 B,D,E 將會輸出到控制臺上。

SymmetricExceptWith

SymmetricExceptWith 方法常用於修改一個 HashSet 來存放兩個 HashSet 都是唯一的元素,換句話說,我要的就是兩個集合都不全有的元素,如果還不明白的話,考慮下面的代碼段:


HashSet<string> setA = new HashSet<string>() { "A", "B", "C", "D", "E" };
HashSet<string> setB = new HashSet<string>() { "A", "X", "C", "Y" };
setA.SymmetricExceptWith(setB);
foreach (string str in setA)
{
  Console.WriteLine(str);
}

當你執行完上面的代碼,你會發現,setA中有而setB中沒有 和 setB中有而setA中沒有的元素將會輸出到控制臺中。

我們知道數組的平均複雜度是 O(N),這裡的 n 表示數組裡的元素數量,而訪問 HashSet 中的某一個元素,它的複雜度為 O(1),這個常量複雜度就決定了 HashSet 在快速檢索 和執行 set集合 操作上是一個非常好的選擇,你也可以使用 List 去存儲某些有指定順序的元素,同時也可以包含重複的值。

相關焦點

  • 如何在 C# 中使用 MSMQ
    MSMQ 是 Windows 自帶的消息隊列,它提供了在多機器,多系統之間實現可靠的消息互聯,MSMQ 支持可擴展,線程安全,使用簡單等強勢特性。MSDN 上說:消息隊列技術使不同時刻運行的程序可以在異構網絡或者異構系統中進行交互,即使對方不在線也沒有關係,應用程式僅關心如何從隊列中讀取或寫入消息。
  • 如何在 C#8 中使用 GUID
    本質上來說,GUID就是一串很長的編號,這串編號從數學角度能夠保證不僅僅在一個系統中是唯一的,而且在多系統甚至跨系統之間都是唯一的,這篇文章我們就來討論一下為什麼需要GUID,以及如何在 C#8 中使用 GUID。
  • 如何在 C# 中使用匿名類型
    匿名類型是一種沒有名字的類型,可以使用 匿名類型 實現在一個類中定義一組只讀屬性,🐂👃的是,這個類你不需要預先定義,本篇就來討論下 匿名類型 是什麼?為什麼這麼重要?如何在 C# 中使用好這個類型。使用 匿名類型現在來研究一下具體代碼,考慮如下的匿名類型。
  • 基礎面試題:聊聊C#中的break、continue、goto的用處
    簡述    c#中break是用來跳出當前的循環,循環結束;continue是跳出本輪循環,循環的下一次還繼續
  • 中文語音識別技術在c#中的應用(二)
    正在閱讀:中文語音識別技術在c#中的應用(二)中文語音識別技術在c#中的應用(二)2004-06-11 10:04出處:CSDN作者:tashanzhishi>  接上篇《中文語音識別技術在c#中的應用(一)》………但是,這個方法本身並不知道你給的字符串是什麼語言,所以需要我們它這個字符串用什麼語言讀出。
  • C#和.NET向JAVA好轉嗎?
    不過也有一些差別,不過不多,稍微習慣下就好了,比如:集合:兩種語言都有集合ArrayList,還有通過鍵訪問值的Java中是HashMap而c#中是HashTable。c#比Java多泛型集合List<T>與Dictionary<K,V>更容易了,無需拆箱裝箱了,更安全了。
  • [搞懂Java集合類6]走近HashSet,TreeSet與LinkedHashSet
    底層調用HashMap的keySet返回所有的key,這點反應了HashSet中的所有元素都是保存在HashMap的key中,value則是使用的PRESENT對象,該對象為static final。hashset基本上就是使用hashmap的方法再次實現了一遍而已,只不過value全都是同一個object,讓你以為相同元素沒有插入,事實上只是value替換成和原來相同的值而已。當add方法發生衝突時,如果key相同,則替換value,如果key不同,則連成鍊表。add()如果此 set 中尚未包含指定元素,則添加指定元素。
  • 面試問:C#結構體和類的區別,結構體的使用場景
    所以結構處理作為基類型對待的小對象,而類處理某個商業邏輯 因為結構是值類型所以結構之間的賦值可以創建新的結構,而類是引用類型,類之間的賦值只是複製引用 註:   a.雖然結構與類的類型不一樣,可是他們的基類型都是對象(object),c#中所有類型的基類型都是object   b.雖然結構的初始化也使用了New 操作符可是結構對象依然分配在堆棧上而不是堆上,如果不使用
  • c#.net多線程編程教學(3):線程同步
    正在閱讀:c#.net多線程編程教學(3):線程同步c#.net多線程編程教學(3):線程同步2005-07-07 10:44出處:作者:c-sharpcorner  考慮一種我們經常遇到的情況:有一些全局變量和共享的類變量,我們需要從不同的線程來更新它們,可以通過使用System.Threading.Interlocked類完成這樣的任務,它提供了原子的,非模塊化的整數更新操作。  還有你可以使用System.Threading.Monitor類鎖定對象的方法的一段代碼,使其暫時不能被別的線程訪問。
  • 在.NET中,C#靜態類使用static定義,什麼是靜態?如何使用它?
    基本概念在.NET應用程式中,使用C#開發,少不了使用靜態類。首先理解清楚什麼是靜態,靜態是相對於實例的,前面我們講解的類,就是實例的類。實例的類,只需要使用class關鍵字定義即可,實例的類必須使用new關鍵字生成對象才可以使用。那什麼是C#靜態的類呢?
  • Unity中的協同程序-使用Promise進行封裝(二)
    在上一篇文章中,我們的注意力主要是放在Unity的協同程序的內部機制以及深入討論了下它們是如何工作的。
  • Unity中的協同程序:如何使用Promise進行封裝(一)
    然而,協同程序會有一些問題,許多程式設計師在使用協同程序的時候會偶然發現。讓我們仔細看看這些問題。那麼在Unity協同程序的內部到底發生了什麼?Unity協同程序到底是如何工作的?我們沒有直接訪問Unity原始碼的權利,但是我們可以從手冊中收集證據,並且通過C#的知識我們可以或多或少的假設它們到底是如何工作的。讓我們儘量精簡這個示例代碼:
  • C# 操作JSON的幾種方式
    關於Json數據在開發中的重要性
  • [譯]聊聊C#中的泛型的使用
    對於非泛型ArrayList類,如果使用對象類型,則可以向集合中添加任何類型,這些類型有時會導致嚴重的問題。當使用foreach語句讀取ArrayList中的值時,將發生拆箱。泛型類可以定義一次,並且可以使用許多不同類型來進行實例化。泛型可以在一種CLR支持的語言中定義,並可以被另一種.NET語言使用。
  • C#中使用單個對象的方法實現Undo/Redo
    【IT168 專稿】  簡介  我們如何在不同的場景下使用這些方法來實現Undo/Redo。這些方法是使用單個對象表示變化,命令模式和備忘錄模式。    正如我們所知,Undo/Redo沒有通用的解決方案,而Undo/Redo在每個應用程式中非常具體。
  • C#中使用命令模式實現Undo/Redo
    第一篇:C#中使用單個對象的方法實現Undo/Redo  為了實現Undo/Redo,我們不得不存儲應用程式的狀態並在撤銷時跳到前一個狀態而在重做時跳到下一個狀態。因此我們需要維護應用程式的狀態來支持Undo/Redo。在所有三種方法中,應用程式狀態的維護用到了兩個棧。一個棧包含用於撤銷操作的狀態,第二個包含用於重做的狀態。
  • NET開發-在C#的LINQ查詢語句中,如何使用group……by……into分組
    Group…by基本語法在C#語言的LINQ查詢語句中使用Group…by子句可以對查詢結果進行分組,相當於SQL Server資料庫表中的Group by語句。在此代碼中,是按Sex欄位的值進行分組,使用s.Key表示分組的鍵,可以循環輸出Sex的值。
  • EntityFramework和EntityFramework.Extended使用說明——性能,語法和產生的sql
    第一種寫法 Join:那如果是多條數據,應使用預加載.使用場景:聯繫人和他的好友.聯繫人一張表,好友關係一張表.聯繫人表和好友關係表做連接,查出多個聯繫人數據(包含他的好友),就應該使用GroupJoin.
  • 在.NET中,C#枚舉類型使用enum定義,具有易維護性,如何使用呢?
    可以將枚舉理解為將一些常用且屬同類型的常量放在一起,當使用時,直接使用枚舉列表列出其中的值,選擇一個即可。例如:顏色值,可以將常見的顏色值放在枚舉中,使用時直接拿出來使用即可,方便代碼的編寫,且易修改,不易出錯。
  • C#中使用Bogus創建模擬數據
    原文:CREATING SAMPLE DATA FOR C#[1] 作者:Bruno Sonnino 譯文:C#中使用