耒阳住房与建设局网站,怎么让人理解网站建设,如何进入网页编辑,网站开发大概需要多久编码数据的格式
程序通常#xff08;至少#xff09;使用两种形式的数据#xff1a;
在内存中#xff0c;数据保存在对象、结构体、列表、数组、散列表、树等中。 这些数据结构针对 CPU 的高效访问和操作进行了优化#xff08;通常使用指针#xff09;。如果要将数据写…编码数据的格式
程序通常至少使用两种形式的数据
在内存中数据保存在对象、结构体、列表、数组、散列表、树等中。 这些数据结构针对 CPU 的高效访问和操作进行了优化通常使用指针。如果要将数据写入文件或通过网络发送则必须将其 编码encode 为某种自包含的字节序列例如JSON 文档。 由于每个进程都有自己独立的地址空间一个进程中的指针对任何其他进程都没有意义所以这个字节序列表示会与通常在内存中使用的数据结构完全不同
所以需要在两种表示之间进行某种类型的翻译。 从内存中表示到字节序列的转换称为 编码Encoding 也称为 序列化serialization 或 编组marshalling反过来称为 解码Decoding解析Parsing反序列化deserialization反编组 (unmarshalling。
这是一个常见的问题因而有许多库和编码格式可供选择。 首先让我们概览一下。
语言特定的格式
许多编程语言都内建了将内存对象编码为字节序列的支持。例如Java 有 java.io.Serializable Ruby 有 MarshalPython 有 pickle等等。许多第三方库也存在例如 Kryo for Java 。
这些编码库非常方便可以用很少的额外代码实现内存对象的保存与恢复。但是它们也有一些深层次的问题
这类编码通常与特定的编程语言深度绑定其他语言很难读取这种数据。如果以这类编码存储或传输数据那你就和这门语言绑死在一起了。并且很难将系统与其他组织的系统可能用的是不同的语言进行集成。为了恢复相同对象类型的数据解码过程需要 实例化任意类 的能力这通常是安全问题的一个来源如果攻击者可以让应用程序解码任意的字节序列他们就能实例化任意的类这会允许他们做可怕的事情如远程执行任意代码。在这些库中数据版本控制通常是事后才考虑的。因为它们旨在快速简便地对数据进行编码所以往往忽略了前向后向兼容性带来的麻烦问题。效率编码或解码所花费的 CPU 时间以及编码结构的大小往往也是事后才考虑的。 例如Java 的内置序列化由于其糟糕的性能和臃肿的编码而臭名昭著。 因此除非临时使用采用语言内置编码通常是一个坏主意。
JSON、XML和二进制变体
当我们谈到可以被多种编程语言读写的标准编码时JSON 和 XML 是最显眼的角逐者。
它们广为人知广受支持也 “广受憎恶”。 XML 经常收到批评过于冗长与且过份复杂。 JSON 的流行则主要源于通过成为 JavaScript 的一个子集Web 浏览器的内置支持以及相对于 XML 的简单性。 CSV 是另一种流行的与语言无关的格式尽管其功能相对较弱。
JSONXML 和 CSV 属于文本格式因此具有人类可读性尽管它们的语法是一个热门争议话题。除了表面的语法问题之外它们也存在一些微妙的问题
数字numbers 编码有很多模糊之处。在 XML 和 CSV 中无法区分数字和碰巧由数字组成的字符串除了引用外部模式。 JSON 虽然区分字符串与数字但并不区分整数和浮点数并且不能指定精度。这在处理大数字时是个问题。例如大于 2^53的整数无法使用 IEEE 754 双精度浮点数精确表示因此在使用浮点数例如 JavaScript的语言进行分析时这些数字会变得不准确。 Twitter 有一个关于大于 2^53的数字的例子它使用 64 位整数来标识每条推文。 Twitter API 返回的 JSON 包含了两个推特 ID一个是 JSON 数字另一个是十进制字符串以解决 JavaScript 程序中无法正确解析数字的问题。
JSON 和 XML 对 Unicode 字符串即人类可读的文本有很好的支持但是它们不支持二进制数据即不带 字符编码character encoding 的字节序列。二进制串是很有用的功能人们通过使用 Base64 将二进制数据编码为文本来绕过此限制。其特有的模式标识着这个值应当被解释为 Base64 编码的二进制数据。这种方案虽然管用但比较 Hacky并且会增加三分之一的数据大小。
XML 和 JSON 都有可选的模式支持。这些模式语言相当强大所以学习和实现起来都相当复杂。 XML 模式的使用相当普遍但许多基于 JSON 的工具才不会去折腾模式。对数据的正确解读例如区分数值与二进制串取决于模式中的信息因此不使用 XML/JSON 模式的应用程序可能需要对相应的编码 / 解码逻辑进行硬编码。
CSV 没有任何模式因此每行和每列的含义完全由应用程序自行定义。如果应用程序变更添加了新的行或列那么这种变更必须通过手工处理。 CSV 也是一个相当模糊的格式如果一个值包含逗号或换行符会发生什么。尽管其转义规则已经被正式指定但并不是所有的解析器都正确的实现了标准。
尽管存在这些缺陷但 JSON、XML 和 CSV 对很多需求来说已经足够好了。它们很可能会继续流行下去特别是作为数据交换格式来说即将数据从一个组织发送到另一个组织。在这种情况下只要人们对格式是什么意见一致格式有多美观或者效率有多高效就无所谓了。让不同的组织就这些东西达成一致的难度超过了绝大多数问题。
二进制编码
对于仅在组织内部使用的数据使用最小公约数式的编码格式压力较小。例如可以选择更紧凑或更快的解析格式。虽然对小数据集来说收益可以忽略不计但一旦达到 TB 级别数据格式的选型就会产生巨大的影响。
JSON 比 XML 简洁但与二进制格式相比还是太占空间。这一事实导致大量二进制编码版本 JSONMessagePack、BSON、BJSON、UBJSON、BISON 和 Smile 等 和 XML例如 WBXML 和 Fast Infoset的出现。这些格式已经在各种各样的领域中采用但是没有一个能像文本版 JSON 和 XML 那样被广泛采用。
这些格式中的一些扩展了一组数据类型例如区分整数和浮点数或者增加对二进制字符串的支持另一方面它们没有改变 JSON / XML 的数据模型。特别是由于它们没有规定模式所以它们需要在编码数据中包含所有的对象字段名称。也就是说下面例子中的 JSON 文档的二进制编码中需要在某处包含字符串 userNamefavoriteNumber 和 interests。
{userName: Martin,favoriteNumber: 1337,interests: [daydreaming, hacking]
}我们来看一个 MessagePack 的例子它是一个 JSON 的二进制编码。下图显示了如果使用 MessagePack 对 上述 JSON 文档进行编码则得到的字节序列。前几个字节如下
第一个字节 0x83 表示接下来是 3 个字段低四位 0x03的 对象 object高四位 0x80。 如果想知道如果一个对象有 15 个以上的字段会发生什么情况字段的数量塞不进 4 个 bit 里那么它会用另一个不同的类型标识符字段的数量被编码两个或四个字节。第二个字节 0xa8 表示接下来是 8 字节长低四位 0x08的字符串高四位 0x0a。接下来八个字节是 ASCII 字符串形式的字段名称 userName。由于之前已经指明长度不需要任何标记来标识字符串的结束位置或者任何转义。接下来的七个字节对前缀为 0xa6 的六个字母的字符串值 Martin 进行编码依此类推。
二进制编码长度为 66 个字节仅略小于文本 JSON 编码所取的 81 个字节删除了空白。所有的 JSON 的二进制编码在这方面是相似的。空间节省了一丁点以及解析加速是否能弥补可读性的损失谁也说不准。
在下面的内容中能达到比这好得多的结果只用 32 个字节对相同的记录进行编码。 Thrift与Protocol Buffers
Apache Thrift 和 Protocol Buffersprotobuf是基于相同原理的二进制编码库。 Protocol Buffers 最初是在 Google 开发的Thrift 最初是在 Facebook 开发的并且都是在 2007~2008 开源的。 Thrift 和 Protocol Buffers 都需要一个模式来编码任何数据。要用Thrift 的上述json进行编码可以使用 Thrift 接口定义语言IDL 来描述模式如下所示
struct Person {1: required string userName,2: optional i64 favoriteNumber,3: optional liststring interests
}Protocol Buffers 的等效模式定义看起来非常相似
message Person {required string user_name 1;optional int64 favorite_number 2;repeated string interests 3;
}
Thrift 和 Protocol Buffers 每一个都带有一个代码生成工具它采用了类似于这里所示的模式定义并且生成了以各种编程语言实现模式的类。你的应用程序代码可以调用此生成的代码来对模式的记录进行编码或解码。 用这个模式编码的数据是什么样的令人困惑的是Thrift 有两种不同的二进制编码格式分别称为 BinaryProtocol 和 CompactProtocol。先来看看 BinaryProtocol。使用这种格式的编码来编码之前的例子只需要 59 个字节如下图所示。 每个字段都有一个类型注释用于指示它是一个字符串、整数、列表等还可以根据需要指定长度字符串的长度列表中的项目数 。出现在数据中的字符串 (“Martin”, “daydreaming”, “hacking”) 也被编码为 ASCII或者说UTF-8与之前类似。
与MessagePack相比最大的区别是没有字段名 (userName, favoriteNumber, interests)。相反编码数据包含字段标签它们是数字 (1, 2 和 3)。这些是模式定义中出现的数字。字段标记就像字段的别名 - 它们是说我们正在谈论的字段的一种紧凑的方式而不必拼出字段名称。
Thrift CompactProtocol 编码在语义上等同于 BinaryProtocol但是如 下图所示它只将相同的信息打包成只有 34 个字节。它通过将字段类型和标签号打包到单个字节中并使用可变长度整数来实现。数字 1337 不是使用全部八个字节而是用两个字节编码每个字节的最高位用来指示是否还有更多的字节。这意味着 - 64 到 63 之间的数字被编码为一个字节-8192 和 8191 之间的数字以两个字节编码等等。较大的数字使用更多的字节。 最后Protocol Buffers只有一种二进制编码格式对相同的数据进行编码如下图所示。 它的打包方式稍有不同但与 Thrift 的 CompactProtocol 非常相似。 Protobuf 将同样的记录塞进了 33 个字节中。 需要注意的一个细节在前面所示的模式中每个字段被标记为必需或可选但是这对字段如何编码没有任何影响二进制数据中没有任何字段指示某字段是否必须。区别在于如果字段设置为 required但未设置该字段则所需的运行时检查将失败这对于捕获错误非常有用。
字段标签和模式演变
模式不可避免地需要随着时间而改变。我们称之为模式演变。 Thrift 和 Protocol Buffers 如何处理模式更改同时保持向后兼容性
从示例中可以看出编码的记录就是其编码字段的拼接。每个字段由其标签号码样本模式中的数字 1,2,3标识并用数据类型例如字符串或整数注释。如果没有设置字段值则简单地从编码记录中省略。从中可以看到字段标记对编码数据的含义至关重要。你可以更改架构中字段的名称因为编码的数据永远不会引用字段名称但不能更改字段的标记因为这会使所有现有的编码数据无效。
你可以添加新的字段到架构只要你给每个字段一个新的标签号码。如果旧的代码不知道你添加的新的标签号码试图读取新代码写入的数据包括一个新的字段其标签号码不能识别它可以简单地忽略该字段。数据类型注释允许解析器确定需要跳过的字节数。这保持了向前兼容性旧代码可以读取由新代码编写的记录。
向后兼容性呢只要每个字段都有一个唯一的标签号码新的代码总是可以读取旧的数据因为标签号码仍然具有相同的含义。唯一的细节是如果你添加一个新的字段你不能设置为必需类似于新增一个数据库的字段不能非空。如果你要添加一个字段并将其设置为必需那么如果新代码读取旧代码写入的数据则该检查将失败因为旧代码不会写入你添加的新字段。因此为了保持向后兼容性在模式的初始部署之后 添加的每个字段必须是可选的或具有默认值。
删除一个字段就像添加一个字段只是这回要考虑的是向前兼容性。这意味着你只能删除可选的字段必需字段永远不能删除而且你不能再次使用相同的标签号码因为你可能仍然有数据写在包含旧标签号码的地方而该字段必须被新代码忽略。
数据类型和模式演变
如何改变字段的数据类型这也许是可能的 —— 详细信息请查阅相关的文档 —— 但是有一个风险值将失去精度或被截断。例如假设你将一个 32 位的整数变成一个 64 位的整数。新代码可以轻松读取旧代码写入的数据因为解析器可以用零填充任何缺失的位。但是如果旧代码读取由新代码写入的数据则旧代码仍使用 32 位变量来保存该值。如果解码的 64 位值不适合 32 位则它将被截断。
Protobuf 的一个奇怪的细节是它没有列表或数组数据类型而是有一个字段的重复标记repeated这是除必需和可选之外的第三个选项。如上一张图所示重复字段的编码正如它所说的那样同一个字段标记只是简单地出现在记录中。这具有很好的效果可以将可选单值字段更改为重复多值字段。读取旧数据的新代码会看到一个包含零个或一个元素的列表取决于该字段是否存在。读取新数据的旧代码只能看到列表的最后一个元素。
Thrift 有一个专用的列表数据类型它使用列表元素的数据类型进行参数化。这不允许 Protocol Buffers 所做的从单值到多值的演变但是它具有支持嵌套列表的优点。
模式的优点
正如我们所看到的Protocol Buffers、Thrift 使用模式来描述二进制编码格式。他们的模式语言比 XML 模式或者 JSON 模式简单得多而后者支持更详细的验证规则例如“该字段的字符串值必须与该正则表达式匹配” 或 “该字段的整数值必须在 0 和 100 之间 “。由于 Protocol BuffersThrift 实现起来更简单使用起来也更简单所以它们已经发展到支持相当广泛的编程语言。
这些编码所基于的想法绝不是新的。例如它们与 ASN.1 有很多相似之处它是 1984 年首次被标准化的模式定义语言。它被用来定义各种网络协议例如其二进制编码DER仍然被用于编码 SSL 证书X.509。 ASN.1 支持使用标签号码的模式演进类似于 Protocol Buffers 和 Thrift 。然而它也非常复杂而且没有好的配套文档所以 ASN.1 可能不是新应用程序的好选择。
许多数据系统也为其数据实现了某种专有的二进制编码。例如大多数关系数据库都有一个网络协议你可以通过该协议向数据库发送查询并获取响应。这些协议通常特定于特定的数据库并且数据库供应商提供将来自数据库的网络协议的响应解码为内存数据结构的驱动程序例如使用 ODBC 或 JDBC API。
所以我们可以看到尽管 JSON、XML 和 CSV 等文本数据格式非常普遍但基于模式的二进制编码也是一个可行的选择。他们有一些很好的属性
它们可以比各种 “二进制 JSON” 变体更紧凑因为它们可以省略编码数据中的字段名称。模式是一种有价值的文档形式因为模式是解码所必需的所以可以确定它是最新的而手动维护的文档可能很容易偏离现实。维护一个模式的数据库允许你在部署任何内容之前检查模式更改的向前和向后兼容性。对于静态类型编程语言的用户来说从模式生成代码的能力是有用的因为它可以在编译时进行类型检查。