.Net Core 最優 MD5 打開方式!初學者建議收藏(支援 SHA1,SHA256,.Net Framework)
- 2019 年 10 月 3 日
- 筆記
public static string GetMd5Hash(string input) { using (MD5 md5Hash = MD5.Create()) { // Convert the input string to a byte array and compute the hash. byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input)); // Create a new Stringbuilder to collect the bytes // and create a string. StringBuilder sBuilder = new StringBuilder(); // Loop through each byte of the hashed data // and format each one as a hexadecimal string. for (int i = 0; i < data.Length; i++) { sBuilder.Append(data[i].ToString("x2")); } // Return the hexadecimal string. return sBuilder.ToString(); } }
這是一段 MSDN 官方的 MD5 示例,例子很簡單且很容易理解。但是,這個例子也有很多的問題,首先上例至少創建了 3 個臨時快取區!且每次執行 GetMd5Hash 都會創建一個 MD5 實例,並在方法執行完成後釋放它。這些都造成了很大的系統資源浪費和增加了 GC 的壓力。
鑒於官方給的 Demo 並不優秀,且網上也沒有給出很好使用方式,這裡我就拿出我多年使用的 MD5 打開方式,這個方法同時支援 SHA1,SHA256 等,即支援 System.Security.Cryptography 命名空間下的 HashAlgorithm(哈希演算法) 實現。也同時支援 .Net Framework 2.0 之後的所有 .Net 平台。
先說明,這個文章是基於 System.Security.Cryptography 命名空間的實現,不是自己寫一個 MD5 演算法哦。
現在我們開始,首先我們先定義一個輔助類:
using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Security.Cryptography; static class THashAlgorithmInstances<THashAlgorithm> where THashAlgorithm : HashAlgorithm { /// <summary> /// 執行緒靜態變數。 /// 即:這個變數在每個執行緒中都是唯一的。 /// 再結合泛型類實現:該變數在不同泛型或不同的執行緒下的值都是不一樣的。 /// 這樣做的目的是為了避開多執行緒問題。
/// 關於垃圾回收:當 .NET 執行緒被釋放時,程式中的所有執行緒靜態變數都會被回收,GC 回收時同時將釋放資源,所以不必擔心釋放問題,GC 會幫助我們的。
/// 這裡描述的 .NET 執行緒釋放不是指 .NET 執行緒回收至執行緒池。很多時候 .NET 的執行緒在程式關閉之前都不會真正釋放,而是在執行緒池中繼續駐留。
/// 執行緒唯一真的能避免多執行緒問題嗎?答:多個執行緒所用存儲空間都不一樣,那麼臟值就不可能存在,如果這都能出現多執行緒問題,我直播吃....豬紅(本人極其厭惡吃豬紅?)。 /// </summary>h [ThreadStatic] static THashAlgorithm instance; public static THashAlgorithm Instance => instance ?? Create(); // C# 語法糖,低版本可以改為 { get { return instance != null ? instance : Create(); } } /// <summary> /// 尋找 THashAlgorithm 類型下的 Create 靜態方法,並執行它。 /// 如果沒找到,則執行 Activator.CreateInstance 調用構造方法創建實例。 /// 如果 Activator.CreateInstance 方法執行失敗,它會拋出異常。 /// </summary> [MethodImpl(MethodImplOptions.NoInlining)] static THashAlgorithm Create() { var createMethod = typeof(THashAlgorithm).GetMethod( nameof(HashAlgorithm.Create), // 這段程式碼同 "Create",低版本 C# 可以替換掉 BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, Type.DefaultBinder, Type.EmptyTypes, null); if (createMethod != null) { instance = (THashAlgorithm)createMethod.Invoke(null, new object[] { }); } else { instance = Activator.CreateInstance<THashAlgorithm>(); } return instance; } }
該輔助類幫助我們避開多執行緒問題,且幫助我們創建指定的 HashAlgorithm 實例。
這裡說明一下,HashAlgorithm.ComputeHash (同 MD5.ComputeHash) 方法絕對不是執行緒安全的!大家使用它的時候必須要注意,在未執行緒同步下調用同一實例的 ComputeHash 方法得到的結果是錯誤的!
關於執行緒唯一和泛型唯一:
還記得老師教我們的時候強調靜態變數就是唯一的,可是現在就突然出現了兩個反例,與之對立,這讓初學者一下子難以接受,其實這也很容易理解的:
首先 [ThreadStatic] 特性我們可以理解為將欄位封裝為了 ThreadLocal<T>,它在內部區分執行緒,然後返回不同的值。
然後泛型唯一,舉例:我們都知道 List<int> 和 List<string> 它們不是一個類型!那麼它們的欄位 List<int>.thread_field 和 List<string>.thread_field 也同理不是一個欄位,那麼它們的值當然也不是同一個啦。
接下來我們再定義實現類:
public static class HashAlgorithmHelper {
{
{
sBuilder.Append(item.ToString(“x2”));
}
}
}
到這裡我們入門級的 MD5 打開方式就完成了,使用方法:HashAlgorithmHelper.ComputeHash<MD5>(“Hello World!”)。
我們來先測試一下:
static void Main(string[] args) { Console.WriteLine(HashAlgorithmHelper.ComputeHash<MD5>("Hello World!")); Console.WriteLine(GetMd5Hash("Hello World!")); while (true) { var stopwatch = Stopwatch.StartNew(); for (int i = 0; i < 1000000; i++) { HashAlgorithmHelper.ComputeHash<MD5>("Hello World!"); } Console.WriteLine(stopwatch.ElapsedMilliseconds); stopwatch = Stopwatch.StartNew(); for (int i = 0; i < 1000000; i++) { GetMd5Hash("Hello World!"); } Console.WriteLine(stopwatch.ElapsedMilliseconds); } }
輸出結果:
可以看出我們的性能已經超官方 Demo 近一倍了。
接下來我們將進入進階級打開方式,我們現在需要自己寫一個簡單的 byte[] To string 方法,我們先打開 C# 項目的 “允許不安全程式碼” 選項。
在解決方案中右鍵項目->屬性->生成->勾選“允許不安全程式碼”。
然後我們在 HashAlgorithmHelper 類中定義新的 ToString 方法。
static string ToString(byte[] bytes) { unsafe { const int byte_len = 2; // 表示一個 byte 的字元長度。 var str = new string('