二进制XML:简化数据存储与传输的新方法

编程的世界中,经常会遇到一个问题:尽管底层数据都是以二进制形式保存和传输的,但处理数据类型(尤其是面向对象编程中的类型)却异常复杂。XML这样的标记语言在处理结构化文本方面非常优雅,它基于上下文无关文法,非常适合词法分析器/解析器处理。然而,当想要超越文本类型,将编码数据嵌入到结构良好的层次结构中时,事情就变得复杂起来。

知道二进制是至关重要的,XML也非常强大,但主流技术并不使用二进制XML来编组数据。尽管在工业标准方面有所进展,但开发者对此并不关心。这可能是因为存在一些方法论上的考虑。想谈谈一些可能会非常熟悉的真实世界场景。

数据库还是XML?

在许多项目中,发现设计和维护关系数据库管理系统(RDBMS)在一些小项目中非常不方便。通常,一个小项目包含不到20个表,并且存储一些图片和文件。如果只有基于文本的信息需要保存,XML是一个很好的选择。但是,图片和文件呢?使用文件系统并在XML节点/属性中记录文件路径更加令人烦恼。

数据库提供了各种类型,但它是关系型的,特别是对于具有复杂业务数据的应用程序。关系模式强大、重量级且不易维护,但适合持久化。编程则完全不同。高效的编程通常基于内存模型,通常是基于各种数据类型的数组/集合。XML在编程场景中更容易操作。然而,知道XML是一种本质上基于文本的“语言”。

如果应用程序需要保存/加载各种类型的数据,如果数据可以以层次结构的方式组织,就像标记语言一样,那么应用程序可能会非常轻量级。将在后面讨论如何使用简单的二进制XML处理技术使其成为可能。

RFC风格的协议与基于XML的WebAPI

有没有参与过通信或分布式系统的开发?传统上,需要指定每个字段的字节偏移量和含义,就像那些大型RFC文档一样。让看看一段RFC 973(TCP)规范:

当阅读并实现协议时,那真是艰难的日子。不想解释图中所示的含义。在这里展示它的原因只是为了给一个直觉,说明在典型的协议设计过程中,数据序列化实现是多么可怕。当出现与无效字节偏移量相关的bug时,调试可能是一场噩梦。

相比之下,让看看Web服务是如何实现协议的,这里是Google Maps API的一段代码:

如此简单,没有字节对齐,没有偏移量计算。一眼就能看到层次结构。这是一个对程序员友好的实现。但是,如何处理传输多媒体消息而不仅仅是文本XML呢?

使用二进制XML工具,不需要任何字节偏移量规范。它就像通常出现在Web服务API中的基于XML的协议设计一样简单。差异是显著的,一方面,程序员享受与XML一样方便的通信,另一方面,任何二进制数据都可以在二进制XML消息中处理,无需额外的代码。

现在是解决方案的时候了。二进制XML由一个简单的树结构表示,具有后代节点和属性。节点内容和属性值可以保存二进制对象。实现了许多预定义类型,如int、double、time、bitmap等,它们以二进制形式保存,但开发者可以使用常见的已知类型进行访问。使用这个工具,加速了小项目的设计和编码,简化了数据结构。

1. IDump

在进入二进制XML之前,让看看IDump.cs。在这里,定义了一个简单的接口。任何继承IDump的类都可以保存到字节列表中或从字节列表中加载。二进制XML经常使用IDump来实现功能。

public interface IDump { int AppendToBytes(ref List byte_segments, ref int index); void LoadFromBytes(byte[] bytes, ref int index); }

2. 内容

每个元素都是内容。内容是一些字节,保存真实数据,并有额外的字节指定内容的类型。有几种预定义类型如下。可以在特定项目中定义自己的。

public enum EnumType { Null, RawBin, IDump, String, Int32, Int64, Double, DateTime, TimeSpan, Bmp, Boolean }

当有类型规范时,可以虚拟地以二进制形式处理任何内容。内容可以通过以下方式简单地创建:

public Content() { } public Content(string val) { this.type = EnumType.String; this.content = System.Text.Encoding.UTF8.GetBytes(val); } public Content(int val) { this.type = EnumType.Int32; this.content = System.BitConverter.GetBytes(val); } public Content(long val) { this.type = EnumType.Int64; this.content = System.BitConverter.GetBytes(val); } public Content(double val) { this.type = EnumType.Double; this.content = System.BitConverter.GetBytes(val); } public Content(DateTime val) { this.type = EnumType.DateTime; this.content = System.BitConverter.GetBytes(val.Ticks); } public Content(TimeSpan val) { this.type = EnumType.TimeSpan; this.content = System.BitConverter.GetBytes(val.Ticks); } public Content(IDump val) { this.type = EnumType.IDump; List ls = new List(); int index = 0; byte[] encoded = System.Text.Encoding.UTF8.GetBytes(val.GetType().FullName); ls.AddRange(System.BitConverter.GetBytes(encoded.Length)); index += 4; ls.AddRange(encoded); index += encoded.Length; val.AppendToBytes(ref ls, ref index); } public Content(System.Drawing.Bitmap bmp) { this.type = EnumType.Bmp; System.IO.MemoryStream ms = new System.IO.MemoryStream(); bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); this.content = ms.ToArray(); } public Content(byte[] val) { this.type = EnumType.RawBin; this.content = val; }

3. 二进制XML

现在有了Content类来保存任何类型的二进制数据。接下来,将使用Content作为元素构建层次结构。

在XML中,有:

<Master ID="1" Name="William Shakespeare" Icon="image-src"> <Works> <Work>Hamlet</Work> <Work>Otello</Work> <Work>King Lear</Work> </Works> </Master>

首先,需要指定一个更强大的ID类型。让ID="1"成为一个更强的64位整数形式。其次,需要Icon属性的二进制内容直接嵌入到XML中,而不是外部链接。让看看二进制XML是如何做的:

BinTree bt = new BinTree("Master"); bt["ID"] = new Content(1L); bt["Name"] = new Content("William Shakespeare"); Bitmap bmp = new Bitmap(100, 100); using (Graphics g = Graphics.FromImage(bmp)) g.Clear(Color.Aqua); bt["Icon"] = new Content(bmp); BinTree bt_tags = bt.FindChildOrAppend("Works"); bt_tags.children.Add(new BinTree("Work") { content = new Content("Hamlet") }); bt_tags.children.Add(new BinTree("Work") { content = new Content("Otello") }); bt_tags.children.Add(new BinTree("Work") { content = new Content("King Lear") });

bt["ID"] = new Content(1L); 这行代码使用-L后缀指定了长类型,即长类型二进制表示形式。然后,由于手头没有William Shakespeare的图标,用背景颜色为Aqua的空白位图绘制了一个图标。图标位图直接保存在Icon属性中。如果愿意,也可以将其保存在一个单独的子节点的内容中,就像通常在XML中所做的那样。

在上面的示例代码中,也看到了二进制XML如何填充后代节点并操作集合。可以看到处理保存、传输和显示等杂项问题是多么简单:

// 转换为字节,然后保存在二进制文件中或远程传输 List bin = new List(); int index = 0; (bt as IDump).AppendToBytes(ref bin, ref index); // 构建一个XML树以方便开发人员 StringBuilder sb = new StringBuilder(); bt.PopulateXml(sb, 0, System.Environment.NewLine, "\n"); Console.WriteLine(sb.ToString());

这里是从二进制XML派生的基于文本的XML。由于Icon是一个二进制位图,这里看到Icon = "{100 x 100}"。

好吧。几乎完成了。剩下的就是简化应用程序。如果使用的是轻量级的关系数据库,请考虑将其迁移到一个简洁的二进制XML文件。如果正在设计一个二进制通信协议,请考虑在标记语言格式中指定详细信息,并传输整个二进制XML数据,而不需要太多规范。

另一个问题是性能。使用关系数据库或RFC风格的协议通常涉及很多性能考虑。本文没有讨论二进制XML是如何内部处理的。可以在源代码中找到。如果有兴趣,给一些提示:

XML是基于词法分析器/解析器的。从计算复杂性的角度看,二进制XML更先进,因为它在源代码中实现了线性索引机制。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485