Silverlight 隔离存储中的二进制序列化 - BinaryFormatter 与 sharpSerializer 对比






4.82/5 (7投票s)
如何在 Silverlight 客户端的 IsolatedStorage 中序列化二进制数据,因为 Silverlight 中没有 BinaryFormatter?为什么不使用 sharpSerializer? - 一个开源的 .NET 和 Silverlight 的 XML 和二进制序列化器
引言
默认情况下,Silverlight 应用程序在其隔离存储中只有 1MB 的可用空间来存储本地数据。如果作为“浏览器外”窗口启动,此金额会增加到 25MB,即使应用程序没有提升权限。您可以通过在 Silverlight 程序上按鼠标右键,然后转到“应用程序存储”注册表来检查可用内存量。
不幸的是,有时需要更多的存储空间。例如 - 公文包模型 - 销售代理从服务器下载数据,在本地与客户一起处理数据,最后将数据同步到服务器。
您始终可以使用 IsolatedStorageFile
类的 IncreaseQuotaTo()
方法为应用程序请求更多可用空间,如以下文章所述:
但是,仅仅增加存储空间大小并不是最优的。比序列化到 XML 更有效的解决方案是更有效地保存数据。
背景
DataContractSerializer
是 Silverlight 客户端序列化业务对象的标准方法。但它会序列化为 XML,而 XML 以浪费数据而闻名。更好的选择是二进制序列化。但有一个问题 - Silverlight 中根本没有 BinaryFormatter
。 :-(
需要第三方软件。让我们以 sharpSerializer 为例 - 一个开源的 .NET、.NET Compact 和 Silverlight 的 XML 和二进制序列化器 - 并将其与完整 .NET Framework 中的 BinaryFormatter
进行比较。
BinaryFormatter 与 sharpSerializer 对比
正如我之前所说 - Silverlight 中没有 BinaryFormatter
。为了获得一些结果,我检查了完整版 .NET Framework 的 BinaryFormatter
的序列化速度和输出文件大小,并将这些值与 sharpSerializer
v.2.6 的结果进行了比较。
序列化的是一个由 10,000 个 RootContainer
类型元素组成的数组 - 这是 sharpSerializer
的 HelloWorldDemo
中一个非常复杂的示例类。我不得不将 RootContainer
及其所有子类标记为 [Serializable]
,因为 BinaryFormatter
的工作需要这样做。顺便说一句,SharpSerializer
不需要对序列化对象进行任何额外属性,也不需要在其 Serialize()
方法中进行类型定义,就像 XmlSerializer
需要的那样。
下面是 RootContainer
的主要部分。要查看其所有子类,请参阅 sharpSerializer
的 源代码(还有另一篇文章描述了如何将像 RootContainer
这样的奇怪类序列化为 XML - 使用 sharpSerializer .NET 进行通用字典、多维数组和继承类型的 XML 序列化)。
// This is an example from the http://www.sharpserializer.com
// Please download the full source code with HelloWorld demo to see more details.
[Serializable]
public class RootContainer
{
public SByte SimpleSByte { get; set; }
public int SimpleInt { get; set; }
public Single SimpleSingle { get; set; }
public double SimpleDouble { get; set; }
public DateTime SimpleDateTime { get; set; }
public TimeSpan SimpleTimeSpan { get; set; }
/// Every enumeration is simple type
public SimpleEnum SimpleEnum { get; set; }
/// Enumeration with FlagsAttribute is SimpleType.
/// It is correct serialized if the result of the flag combination
/// has unique int value,
/// i.e. Flag1 = 2, Flag2 = 4, Flag3 = 8 ...
public FlagEnum FlagsEnum { get; set; }
/// Decimal is 16 bytes long
public decimal SimpleDecimal { get; set; }
public string SimpleString { get; set; }
public string EmptyString { get; set; }
/// Structures are handled as objects during serialization
/// They are serialized as ComplexProperty
public AdvancedStruct AdvancedStruct { get; set; }
/// One dimensional array of simple type.
/// It is serialized as SingleDimensionalArrayProperty
public string[] SingleArray { get; set; }
/// Multidimensional array of simple type.
/// Is is serialized as MultiDimensionalArrayProperty
public string[,] DoubleArray { get; set; }
/// Single array of derived objects.
/// This is polymorphic collection - Items derive from the interface
public IComplexObject[] PolymorphicSingleArray { get; set; }
/// Generic list is serialized as a collection.
/// It is serialized as CollectionProperty
public IList<string> GenericList { get; set; }
/// Generic Dictionary of simple types.
/// Is is serialized as DictionaryProperty
public IDictionary<int, string> GenericDictionary { get; set; }
/// Generic dictionary where values are inherited from the value type
public IDictionary<int,
IComplexObject> GenericDictionaryOfPolymorphicValues { get; set; }
/// Polymorphic property. Object instance derives from the property type
/// Is serialized as ComplexProperty
public IComplexObject ComplexObject { get; set; }
/// Collection where item values are
/// derived from the collection item type
public ComplexObjectPolymorphicCollection ComplexObjectCollection { get; set; }
/// Dictionary where values are derived
/// from the predefined dictionary value type
public ComplexObjectPolymorphicDictionary ComplexObjectDictionary { get; set; }
/// List items are derived from the generic attribute.
/// This is polymorphic attribute.
public IList<IComplexObject> GenericListOfComplexObjects { get; set; }
/// Generic object with polymorphic attribute.
/// It is serialized as ComplexProperty
public GenericObject<IComplexObject> GenericObjectOfComplexObject { get; set; }
/// Multidimensional array of generic object with polymorphic attribute
public GenericObject<IComplexObject>[,]
MultiArrayOfGenericObjectWithPolymorphicArgument { get; set; }
/// Array of objects where every item can be of other type
/// It is serialized as SingleDimensionalArrayProperty
public object[] SingleArrayOfObjects { get; set; }
}
为了测试目的,我制作了一个 小型基准测试控制台程序 来测量序列化和反序列化时间,并将序列化结果保存为文件 - 以比较它们的大小。
“Main
”方法生成 10,000 个 RootContainer
元素并调用单个测试方法。
static void Main(string[] args)
{
// create 10.000 items
RootContainer[] containerArray = createContainerArray(10000);
// Serialization tests
Console.WriteLine("Serializing");
serializeWithBinaryFormatter(containerArray, "BinaryFormatter.bin");
serializeWithSharpSerializer(containerArray,
BinarySerializationMode.Burst, "sharpSerializerBurst.bin");
serializeWithSharpSerializer(containerArray,
BinarySerializationMode.SizeOptimized, "sharpSerializerOptimized.bin");
serializeWithXmlSharpSerializer(containerArray, "sharpSerializer.xml");
// Deserialization tests
Console.WriteLine();
Console.WriteLine("Deserializing");
deserializeWithBinaryFormatter("BinaryFormatter.bin");
deserializeWithSharpSerializer(BinarySerializationMode.Burst,
"sharpSerializerBurst.bin");
deserializeWithSharpSerializer(BinarySerializationMode.SizeOptimized,
"sharpSerializerOptimized.bin");
deserializeWithXmlSharpSerializer("sharpSerializer.xml");
}
以下方法使用 BinaryFormatter
进行序列化。注意!Stopwatch
是 .NET 中测量时间的最佳类。
private static void serializeWithBinaryFormatter
(RootContainer[] containerArray, string shortFilename)
{
// convert short filename to full filename
string filename = getFilename(shortFilename);
// creating a stream, as BinaryFormatter can not serialize directly to a file
using (var stream = new FileStream(filename, FileMode.Create))
{
var formatter = new BinaryFormatter();
// Stopwatch as a stopper is enough accurate
Stopwatch watch = new Stopwatch();
Console.WriteLine("Starting serialization with BinaryFormatter");
watch.Start();
// Serializing
formatter.Serialize(stream, containerArray);
watch.Stop();
Console.WriteLine(string.Format
("Stopped after {0}ms", watch.ElapsedMilliseconds));
}
}
为了使用 sharpSerializer
序列化测试对象,我创建了以下方法。顺便说一句,SharpSerializer
的语法比 BinaryFormatter
容易得多。
private static void serializeWithSharpSerializer
(RootContainer[] containerArray, BinarySerializationMode mode, string shortFilename)
{
string filename = getFilename(shortFilename);
// create sharpSerializer with an overloaded constructor
var serializer = new SharpSerializer(mode)
Stopwatch watch = new Stopwatch();
Console.WriteLine(string.Format("Starting serialization with SharpSerializer ({0})",
Enum.GetName(typeof(BinarySerializationMode), mode)));
watch.Start();
// Serializing
serializer.Serialize(containerArray, filename);
watch.Stop();
Console.WriteLine(string.Format("Stopped after {0}ms", watch.ElapsedMilliseconds));
}
程序屏幕上的序列化和反序列化时间

序列化和反序列化结果表格(对于序列化其他对象,结果及其比例可能会有所不同)
序列化器 | 序列化时间 [秒] | 文件大小 [MB] | 反序列化时间 [秒] |
.NET BinaryFormatter | 2.1 | 19.3 | 7.9 |
sharpSerializer - 二进制 (Burst) | 3.1 | 3.8 | 3.7 |
sharpSerializer - 二进制 (SizeOptimized) | 3.8 | 8.5 | 2.6 |
sharpSerializer - XML | 3.7 | 86.0 | 17.6 |
最快的序列化时间是 2.1 秒,由 BinaryFormatter
提供,但其文件大小为 19.3MB,是 sharpSerializer
在其二进制 SizeOptimized
模式下产生的两倍(您可以在项目页面的 教程 中找到更多关于 sharpSerializer
二进制模式的信息)。
BinaryFormatter
的反序列化时间是 sharpSerializer
的最佳时间 2.6 秒的 3 倍多。
结果分析
服务器端
在可以使用完整 .NET Framework 的地方,您可以在 BinaryFormatter
和 sharpSerializer
之间进行选择来进行二进制序列化。如果您只写入数据一次,然后多次读取(例如照片库或 MP3 音乐商店)- 那么 sharpSerializer
比内置的 BinaryFormatter
是更好的选择。它比 BinaryFormatter
慢 80%,但读取速度快 300%。更不用说文件大小,使用 sharpSerializer
序列化时文件大小会减小 50%。
Silverlight 客户端侧
在 Silverlight 中,如果您想使用二进制序列化,别无选择。由于 Silverlight 中没有 BinaryFormatter
,唯一的解决方案是 sharpSerializer
。
sharpSerializer 的优势
- 语法非常简单
- 无需用额外属性标记序列化对象
- 无需在
Serialize()
方法中声明序列化类型 - 生成的文件大小很小
- 对象反序列化速度快
- .NET Full、.NET Compact 和 Silverlight 之间的通用算法和序列化兼容性
- 开源且免费用于任何目的
sharpSerializer 的缺点
- 只序列化
public
属性 - 只能反序列化具有标准构造函数的对象
- XML 和二进制格式与其他 .NET 标准不兼容
结论
除了许多优点和良好的基准测试之外,sharpSerializer
也有一些缺点。这取决于具体情况,这些缺点是否是绊脚石。
由于我主要使用 sharpSerializer
来存储我自己的业务对象,所以为每个对象提供标准构造函数对我来说不是问题。使用仅 public
属性来存储对象的状态也没有问题。
如果需要序列化复杂的三方类,应该使用适配器模式将其转换为简单的类。适配器模式在大多数情况下可以简化输出并减小其大小,因为只序列化了需要的属性。
sharpSerializer
已不再是 Beta 版,当前版本是 2.6。它稳定可靠。
sharpSerializer
未来里程碑之一是数据压缩。实际上,在为 Silverlight 找到 DeflateStream
的开源替代品方面存在问题,因为 GZipStream
和 DeflateStream
在 Silverlight 4 及更早版本中不受支持。实现数据压缩后,sharpSerializer
的优势列表可以扩展为“生成的文件大小非常非常小”。 :-)
来自作者
还有另一篇关于使用 sharpSerializer 进行 XML 序列化的文章
当然,鼓励您自己对 sharpSerializer Framework 进行测试。如果您喜欢这篇文章,请评分,如果不喜欢 - 请在下面的评论区告诉我原因。 ;-)
历史
- 2010 年 10 月 6 日 - 首次发布