我有这堂课:
public class SomeClass
{
public string Str1 { get; set; }
public string Str2 { get; set; }
public string Str3 { get; set; }
public string Str4 { get; set; }
}
我想创建一个hashkey,它作为varbinary(20)保存在数据库中,以确定类的唯一性(不区分大小写)。我想在这种情况下不能使用通常的GetHashCode方法。在这种情况下最佳做法是什么?
答案 0 :(得分:3)
简单示例:
public class SomeClass
{
public string Str1 { get; set; }
public string Str2 { get; set; }
public string Str3 { get; set; }
public string Str4 { get; set; }
public byte[] SHA256()
{
using (var sha256 = new SHA256Managed())
{
var strings = new[] { Str1, Str2, Str3, Str4 };
for (int i = 0; i < strings.Length; i++)
{
string str = strings[i];
if (str != null)
{
// Commented lines are for using ToUpperInvariant()
//str = str.ToUpperInvariant()
byte[] length2 = BitConverter.GetBytes(str.Length);
sha256.TransformBlock(length2, 0, length2.Length, length2, 0);
// byte[] sortKeyBytes = Encoding.UTF8.GetBytes(str);
byte[] sortKeyBytes = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(str, CompareOptions.IgnoreCase).KeyData;
sha256.TransformBlock(sortKeyBytes, 0, sortKeyBytes.Length, sortKeyBytes, 0);
}
else
{
byte[] length2 = BitConverter.GetBytes(-1);
sha256.TransformBlock(length2, 0, length2.Length, length2, 0);
}
}
sha256.TransformFinalBlock(new byte[0], 0, 0);
byte[] hash = sha256.Hash;
return hash;
}
}
}
我使用SHA256,解决方案基于@usr在https://stackoverflow.com/a/10452967/613130中建议的解决方案。生成的哈希码长度为32个字节,但您可以将其截断为20(显然您将减少其唯一性)。
我将各种字符串的长度添加到字符串中。这样{ "ABCD", "", "", "" }
将生成与{ "A", "B", "C", "D" }
不同的哈希值。
如果你愿意,你可以使用好的旧ToUpperInvariant()
和基于它的哈希(代码中有一些注释行...你取消注释它们,移除byte[] sortKeyBytes = CultureInfo.InvariantCulture
并过得开心:-) )。
我必须说实话,我不确定&#34;稳定性&#34; GetSortKey
... GetSortKey
将在5年内返回相同的权重,在.NET 10.0中使用Unicode 11.0?谁知道?我当然不会!
MSDN表明他们可以改变:
如果应用程序序列化SortKey对象,当有新版本的.NET Framework时,应用程序必须重新生成所有排序键。
所以最后我建议基于.ToUpperInvariant()
的替代解决方案(要清楚,如果我的老板让我这样做,我会告诉他:使用.ToUpperInvariant()
)。请注意,即使使用.ToUpperInvariant()
,将来也可能会发生微小变化。可以为现有的小写字符引入新的大写字符。请参阅http://unicode.org/faq/casemap_charprop.html&#34;如果其中一对已经编码,是否可以添加案例对?&#34;
答案 1 :(得分:2)
varbinary(20)
是160位,因此您正在寻找160位哈希算法。 SHA-1算法产生160位散列值。
您的问题的目的似乎是创建一个预期对于SomeClass
的给定实例唯一的哈希值,因此您应该支持快速哈希算法而不是加密哈希算法。 SHA-1是一种加密算法,但速度非常快,.NET Framework中有一个实现。此外,还存在对SHA-1算法的攻击,因此您不应将其用于加密目的,而是选择SHA-256等算法(速度较慢)。
总而言之,我相信SHA-1非常适合您的问题。使用该算法很简单。 1)连接字符串,2)将它们转换为大写,3)使用合适的编码将它们转换为字节(我使用UTF-8)和4)计算哈希:
Byte[] GetHash(SomeClass someClass) {
if (someClass == null)
throw new ArgumentNullException("someClass");
var byteBuffers = GetStrings(someClass).Select(
s => String.IsNullOrEmpty(s)
? new Byte[0] : Encoding.UTF8.GetBytes(s.ToUpperInvariant())
);
var bytes = byteBuffers
.Aggregate(new List<Byte>(), (l, b) => { l.AddRange(b); return l; })
.ToArray();
using (var sha1 = new SHA1Managed())
return sha1.ComputeHash(bytes);
}
IEnumerable<String> GetStrings(SomeClass someClass) {
yield return someClass.Str1;
yield return someClass.Str2;
yield return someClass.Str3;
yield return someClass.Str4;
}
请注意,任何哈希算法(也称为加密算法)都会产生冲突。
Xanatos有一个非常好的观点:
我将各种字符串的长度添加到字符串中。这样
{ "ABCD", "", "", "" }
将生成与{ "A", "B", "C", "D" }
不同的哈希值。
这是一个替代解决方案,以稍微不同的方式解决相同的问题,其中每个字符串长度模256包含在散列中:
Byte[] GetHash(SomeClass someClass) {
if (someClass == null)
throw new ArgumentNullException("someClass");
var byteBuffers = GetBuffers(GetStrings(someClass));
var bytes = byteBuffers
.Aggregate(new List<Byte>(), (l, b) => { l.AddRange(b); return l; })
.ToArray();
using (var sha1 = new SHA1Managed())
return sha1.ComputeHash(bytes);
}
IEnumerable<String> GetStrings(SomeClass someClass) {
yield return someClass.Str1?.ToUpperInvariant();
yield return someClass.Str2?.ToUpperInvariant();
yield return someClass.Str3?.ToUpperInvariant();
yield return someClass.Str4?.ToUpperInvariant();
}
IEnumerable<Byte[]> GetBuffers(IEnumerable<String> strings) {
foreach (var @string in strings) {
if (!String.IsNullOrEmpty(@string)) {
yield return new[] { (Byte) (@string.Length%256) };
yield return Encoding.UTF8.GetBytes(@string);
}
else
yield return new Byte[1];
}
}