您可以使用http://code.google.com/p/protobuf-net/ 代替二进制序列化,然后计算它的加密哈希。据说 protobuf 比 Bin Ser 更紧凑(参见例如 http://code.google.com/p/protobuf-net/wiki/Performance )。
考虑到您实际上并不需要序列化,我会补充一点。最好使用反射并“导航”通过计算您的哈希的对象(以相同的方式各种序列化程序“遍历”您的对象)。参见例如Using reflection in C# to get properties of a nested object
经过深思熟虑,听了@Jon 的话,我可以告诉你,我的“次要”想法(使用反射)非常非常非常困难,除非你想花一周时间编写对象解析器。是的,它是可行的......但是在计算哈希之前你会给数据什么表示?说清楚:
two strings
"A"
"B"
显然是“A”、“B”!=“AB”、“”。但是 MD5("A") 结合了 MD5("B") == MD5("AB") 结合了 MD5("")。可能最好的方法是在前面加上长度(所以使用 Pascal/BSTR 表示法)
还有null 值?他们有什么“序列化”价值?另一个问题。显然,如果您将字符串序列化为长度+字符串(以便解决前面的问题),您可以简单地将 null 序列化为"null"(无长度)......对象呢?你会在前面加上一个对象类型 ID 吗?肯定会更好。否则可变长度对象可能会造成与字符串一样的混乱。
使用 BinaryFormatter(甚至可能是 protobuf-net)您不必真正将序列化对象保存在某处,因为它们都支持流式传输......一个例子
public class Hasher : Stream
{
protected readonly HashAlgorithm HashAlgorithm;
protected Hasher(HashAlgorithm hash)
{
HashAlgorithm = hash;
}
public static byte[] GetHash(object obj, HashAlgorithm hash)
{
var hasher = new Hasher(hash);
if (obj != null)
{
var bf = new BinaryFormatter();
bf.Serialize(hasher, obj);
}
else
{
hasher.Flush();
}
return hasher.HashAlgorithm.Hash;
}
public override bool CanRead
{
get { throw new NotImplementedException(); }
}
public override bool CanSeek
{
get { throw new NotImplementedException(); }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
HashAlgorithm.TransformFinalBlock(new byte[0], 0, 0);
}
public override long Length
{
get { throw new NotImplementedException(); }
}
public override long Position
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
HashAlgorithm.TransformBlock(buffer, offset, count, buffer, offset);
}
}
static void Main(string[] args)
{
var list = new List<int>(100000000);
for (int i = 0; i < list.Capacity; i++)
{
list.Add(0);
}
Stopwatch sw = Stopwatch.StartNew();
var hash = Hasher.GetHash(list, new MD5CryptoServiceProvider());
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
我定义了一个Hasher 类,它接收对象的序列化(一次一个片段)并以“流模式”计算散列。内存使用量为 O(1)。时间显然是 O(n) (其中 n 是序列化对象的“大小”)。
如果您想使用 protobuf(但请注意,对于复杂的对象,它需要使用其属性(或 WCF 属性或...)标记它们)
public static byte[] GetHash<T>(T obj, HashAlgorithm hash)
{
var hasher = new Hasher(hash);
if (obj != null)
{
ProtoBuf.Serializer.Serialize(hasher, obj);
hasher.Flush();
}
else
{
hasher.Flush();
}
return hasher.HashAlgorithm.Hash;
}
唯一的“大”区别是 protobuf 不Flush 流,所以我们必须这样做,并且它确实希望输入根对象而不是简单的“对象”。
哦...对于您的问题:
我应该如何序列化对象?它
一定要快,不要消耗太多
记忆。它还必须始终可靠
以同样的方式序列化。如果我使用
.NET 默认序列化可以
确实确保创建的二进制文件
流总是相同的,如果
实际数据是一样的吗?我怀疑。
List<int> l1 = new List<int>();
byte[] bytes1, bytes2;
using (MemoryStream ms = new MemoryStream())
{
new BinaryFormatter().Serialize(ms, l1);
bytes1 = ms.ToArray();
}
l1.Add(0);
l1.RemoveAt(0);
using (MemoryStream ms = new MemoryStream())
{
new BinaryFormatter().Serialize(ms, l1);
bytes2 = ms.ToArray();
}
Debug.Assert(bytes1.Length == bytes2.Length);
可以这样说:Debug.Assert 将失败。这是因为 List “保存”了一些内部状态(例如版本)。这使得二进制序列化和比较非常困难。您最好使用“可编程”序列化程序(如 proto-buf)。你告诉他要序列化哪些属性/字段,他会序列化它们。
那么,什么是序列化的替代方法,不需要很长时间来实现?
Proto-buf... 或 DataContractSerializer(但它很慢)。可以想象,数据序列化没有灵丹妙药。