标签 XML 下的文章

在你使用 Java 编写软件时实现持久化配置。

 title=

当你编写一个应用时,你通常都会希望用户能够定制化他们和应用交互的方式,以及应用与系统进行交互的方式。这种方式通常被称为 “ 偏好 preference ” 或者 “ 设置 setting ”,它们被保存在一个 “偏好文件” 或者 “配置文件” 中,有时也直接简称为 “ 配置 config ”。配置文件可以有很多种格式,包括 INI、JSON、YAML 和 XML。每一种编程语言解析这些格式的方式都不同。本文主要讨论,当你在使用 Java 编程语言 来编写软件时,实现持久化配置的方式。

选择一个格式

编写配置文件是一件相当复杂的事情。我曾经试过把配置项使用逗号分隔保存在一个文本文件里,也试过把配置项保存在非常详细的 YAML 和 XML 中。对于配置文件来说,最重要是要有一致性和规律性,它们使你可以简单快速地编写代码,从配置文件中解析出数据;同时,当用户决定要做出修改时,很方便地保存和更新配置。

目前有 几种流行的配置文件格式。对于大多数常见的配置文件格式,Java 都有对应的 library 。在本文中,我将使用 XML 格式。对于一些项目,你可能会选择使用 XML,因为它的一个突出特点是能够为包含的数据提供大量相关的元数据,而在另外一些项目中,你可能会因为 XML 的冗长而不选择它。在 Java 中使用 XML 是非常容易的,因为它默认包含了许多健壮的 XML 库。

XML 基础

讨论 XML 可是一个大话题。我有一本关于 XML 的书,它有超过 700 页的内容。幸运的是,使用 XML 并不需要非常了解它的诸多特性。就像 HTML 一样,XML 是一个带有开始和结束标记的分层标记语言,每一个标记(标签)内可以包含零个或更多数据。下面是一个 XML 的简单示例片段:

<xml>
  <node>
    <element>Penguin</element>
  </node>
</xml>

在这个 自我描述的 self-descriptive 例子中,XML 解析器使用了以下几个概念:

  • 文档 Document <xml> 标签标志着一个 文档 的开始,</xml> 标签标志着这个文档的结束。
  • 节点 Node <node> 标签代表了一个 节点
  • 元素 Element <element>Penguin</element> 中,从开头的 < 到最后的 > 表示了一个 元素
  • 内容 Content : 在 <element> 元素里,字符串 Penguin 就是 内容

不管你信不信,只要了解了以上几个概念,你就可以开始编写、解析 XML 文件了。

创建一个示例配置文件

要学习如何解析 XML 文件,只需要一个极简的示例文件就够了。假设现在有一个配置文件,里面保存的是关于一个图形界面窗口的属性:

<xml>
  <window>
    <theme>Dark</theme>
    <fullscreen>0</fullscreen>
    <icons>Tango</icons>
</window>
</xml>

创建一个名为 ~/.config/DemoXMLParser 的目录:

$ mkdir ~/.config/DemoXMLParser

在 Linux 中,~/.config 目录是存放配置文件的默认位置,这是在 自由桌面工作组 的规范中定义的。如果你正在使用一个不遵守 自由桌面工作组 Freedesktop 标准的操作系统,你也仍然可以使用这个目录,只不过你需要自己创建这些目录了。

复制 XML 的示例配置文件,粘贴并保存为 ~/.config/DemoXMLParser/myconfig.xml 文件。

使用 Java 解析 XML

如果你是 Java 的初学者,你可以先阅读我写的 面向 Java 入门开发者的 7 个小技巧。一旦你对 Java 比较熟悉了,打开你最喜爱的集成开发工具(IDE),创建一个新工程。我会把我的新工程命名为 myConfigParser

刚开始先不要太关注依赖导入和异常捕获这些,你可以先尝试用 javaxjava.io 包里的标准 Java 扩展来实例化一个解析器。如果你使用了 IDE,它会提示你导入合适的依赖。如果没有,你也可以在文章稍后的部分找到完整的代码,里面就有完整的依赖列表。

Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
File configFile = new File(configPath.toString(), "myconfig.xml");

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

DocumentBuilder builder = null;
builder = factory.newDocumentBuilder();

Document doc = null;
doc = builder.parse(configFile);
doc.getDocumentElement().normalize();

这段示例代码使用了 java.nio.Paths 类来找到用户的主目录,然后在拼接上默认配置文件的路径。接着,它用 java.io.File 类来把配置文件定义为一个 File 对象。

紧接着,它使用了 javax.xml.parsers.DocumentBuilderjavax.xml.parsers.DocumentBuilderFactory 这两个类来创建一个内部的文档构造器,这样 Java 程序就可以导入并解析 XML 数据了。

最后,Java 创建一个叫 doc 的文档对象,并且把 configFile 文件加载到这个对象里。通过使用 org.w3c.dom 包,它读取并规范化了 XML 数据。

基本上就是这样啦。理论上来讲,你已经完成了数据解析的工作。可是,如果你不能够访问数据的话,数据解析也没有多少用处嘛。所以,就让我们再来写一些查询,从你的配置中读取重要的属性值吧。

使用 Java 访问 XML 的值

从你已经读取的 XML 文档中获取数据,其实就是要先找到一个特定的节点,然后遍历它包含的所有元素。通常我们会使用多个循环语句来遍历节点中的元素,但是为了保持代码可读性,我会尽可能少地使用循环语句:

NodeList nodes = doc.getElementsByTagName("window");

for (int i = 0; i < nodes.getLength(); i++) {
 Node mynode = nodes.item(i);
 System.out.println("Property = " + mynode.getNodeName());
       
 if (mynode.getNodeType() == Node.ELEMENT_NODE) {
   Element myelement = (Element) mynode;
             
   System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
   System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
   System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
 }
}

这段示例代码使用了 org.w3c.dom.NodeList 类,创建了一个名为 nodesNodeList 对象。这个对象包含了所有名字匹配字符串 window 的子节点,实际上这样的节点只有一个,因为本文的示例配置文件中只配置了一个。

紧接着,它使用了一个 for 循环来遍历 nodes 列表。具体过程是:根据节点出现的顺序逐个取出,然后交给一个 if-then 子句处理。这个 if-then 子句创建了一个名为 myelementElement 对象,其中包含了当前节点下的所有元素。你可以使用例如 getChildNodesgetElementById 方法来查询这些元素,项目中还 记录了 其他查询方法。

在这个示例中,每个元素就是配置的键。而配置的值储存在元素的内容中,你可以使用 .getTextContent 方法来提取出配置的值。

在你的 IDE 中运行代码(或者运行编译后的二进制文件):

$ java ./DemoXMLParser.java
Property = window
Theme = Dark
Fullscreen = 0
Icon set = Tango

下面是完整的代码示例:

package myConfigParser;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class ConfigParser {

        public static void main(String[] args) {
                Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
                File configFile = new File(configPath.toString(), "myconfig.xml");
                DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = null;
               
                try {
                        builder = factory.newDocumentBuilder();
                } catch (ParserConfigurationException e) {
                        e.printStackTrace();
                }
       
                Document doc = null;
       
                try {
                        doc = builder.parse(configFile);
                } catch (SAXException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        doc.getDocumentElement().normalize();
       
        NodeList nodes = doc.getElementsByTagName("window");
        for (int i = 0; i < nodes.getLength(); i++) {
           Node mynode = nodes.item(i);
           System.out.println("Property = " + mynode.getNodeName());
           
           if (mynode.getNodeType() == Node.ELEMENT_NODE) {
               Element myelement = (Element) mynode;

               System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
               System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
               System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
           } // close if
        } // close for
    } // close method
} //close class

使用 Java 更新 XML

用户时不时地会改变某个偏好项,这时候 org.w3c.dom 库就可以帮助你更新某个 XML 元素的内容。你只需要选择这个 XML 元素,就像你读取它时那样。不过,此时你不再使用 .getTextContent 方法,而是使用 .setTextContent 方法。

updatePref = myelement.getElementsByTagName("fullscreen").item(0);
updatePref.setTextContent("1");

System.out.println("Updated fullscreen to " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());  

这么做会改变应用程序内存中的 XML 文档,但是还没有把数据写回到磁盘上。配合使用 javaxw3c 库,你就可以把读取到的 XML 内容写回到配置文件中。

TransformerFactory transformerFactory = TransformerFactory.newInstance();

Transformer xtransform;
xtransform = transformerFactory.newTransformer();

DOMSource mydom = new DOMSource(doc);
StreamResult streamResult = new StreamResult(configFile);

xtransform.transform(mydom, streamResult);

这么做会没有警告地写入转换后的数据,并覆盖掉之前的配置。

下面是完整的代码,包括更新 XML 的操作:

package myConfigParser;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class ConfigParser {

        public static void main(String[] args) {
                Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
                File configFile = new File(configPath.toString(), "myconfig.xml");
                DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = null;
               
                try {
                        builder = factory.newDocumentBuilder();
                } catch (ParserConfigurationException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
       
                Document doc = null;
       
                try {
                        doc = builder.parse(configFile);
                } catch (SAXException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        doc.getDocumentElement().normalize();
        Node updatePref = null;
//        NodeList nodes = doc.getChildNodes();
        NodeList nodes = doc.getElementsByTagName("window");
        for (int i = 0; i < nodes.getLength(); i++) {
           Node mynode = nodes.item(i);
           System.out.println("Property = " + mynode.getNodeName());
           
           if (mynode.getNodeType() == Node.ELEMENT_NODE) {
               Element myelement = (Element) mynode;

               System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
               System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
               System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());

               updatePref = myelement.getElementsByTagName("fullscreen").item(0);
               updatePref.setTextContent("2");
               System.out.println("Updated fullscreen to " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());          
           } // close if
           
        }// close for

        // write DOM back to the file
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer xtransform;

        DOMSource mydom = new DOMSource(doc);
        StreamResult streamResult = new StreamResult(configFile);

        try {
                xtransform = transformerFactory.newTransformer();
                xtransform.transform(mydom, streamResult);
        } catch (TransformerException e) {
                e.printStackTrace();
        }
                       
    } // close method
} //close class

如何保证配置不出问题

编写配置文件看上去是一个还挺简单的任务。一开始,你可能会用一个简单的文本格式,因为你的应用程序只要寥寥几个配置项而已。但是,随着你引入了更多的配置项,读取或者写入错误的数据可能会给你的应用程序带来意料之外的错误。一种帮助你保持配置过程安全、不出错的方法,就是使用类似 XML 的规范格式,然后依靠你用的编程语言的内置功能来处理这些复杂的事情。

这也正是我喜欢使用 Java 和 XML 的原因。每当我试图读取错误的配置值时,Java 就会提醒我。通常,这是由于我在代码中试图获取的节点,并不存在于我期望的 XML 路径中。XML 这种高度结构化的格式帮助了代码保持可靠性,这对用户和开发者来说都是有好处的。


via: https://opensource.com/article/21/7/parsing-config-files-java

作者:Seth Kenlon 选题:lujun9972 译者:lkxed 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

借助终端上的 XML 工具包 XMLStarlet,你就是 XML 之星。

 title=

学习解析 XML 通常被认为是一件复杂的事情,但它不一定是这样。XML 是高度严格结构化的,所以也是相对来说可预测的。也有许多其他工具可以帮助你使这项工作易于管理。

我最喜欢的 XML 实用程序之一是 XMLStarlet,这是一个用于终端的 XML 工具包,借助这个 XML 工具包,你可以验证、解析、编辑、格式化和转换 XML 数据。XMLStarLet 是个相对较小的命令,但浏览 XML 却充满潜力,因此本文演示了如何使用它来查询 XML 数据。

安装

XMLStarLet 默认安装在 CentOS、Fedora,和许多其他现代 Linux 发行版上,所以你可以打开终端,输入 xmlstarlet 来访问它。如果 XMLStarLet 还没有被安装,你的操作系统则会为你安装它。

或者,你可以用包管理器安装 xmlstarlet

$ sudo dnf install xmlstarlet

在 macOS 上,可以使用 MacPortsHomebrew。在 Windows 上,可以使用 Chocolatey

如果都失败了,你可以从 Sourceforge 上的源代码 手动安装它。

用 XMLStarlet 解析 XML

有许多工具可以帮助解析和转换 XML 数据,包括允许你 编写自己的解析器 的软件库,和复杂的命令,如 fopxsltproc。不过有时你不需要处理 XML 数据;你只需要一个方便的方法从 XML 数据中来提取、更新或验证重要数据。对于随手的 XML 交互,我使用 xmlstarlet,这是常见的处理 XML任务的一个典型的“瑞士军刀”式应用。通过运行 --help 命令,你可以看到它提供哪些选项:

$ xmlstarlet --help
Usage: xmlstarlet [<options>] <command> [<cmd-options>]
where <command> is one of:
  ed    (or edit)      - Edit/Update XML document(s)
  sel   (or select)    - Select data or query XML document(s) (XPATH, etc)
  tr    (or transform) - Transform XML document(s) using XSLT
  val   (or validate)  - Validate XML document(s) (well-formed/DTD/XSD/RelaxNG)
  fo    (or format)    - Format XML document(s)
  el    (or elements)  - Display element structure of XML document
  c14n  (or canonic)   - XML canonicalization
  ls    (or list)      - List directory as XML
[...]

你可以通过在这些子命令的末尾附加 -help 来获得进一步的帮助:

$ xmlstarlet sel --help
  -Q or --quiet             - do not write anything to standard output.
  -C or --comp              - display generated XSLT
  -R or --root              - print root element &lt;xsl-select&gt;
  -T or --text              - output is text (default is XML)
  -I or --indent            - indent output
[...]

用 sel 命令选择数据

可以使用 xmlstarlet select(简称 sel)命令查看 XML 格式的数据。下面是一个简单的 XML 文档:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xml>
  <os>
   <linux>
    <distribution>
      <name>Fedora</name>
      <release>7</release>
      <codename>Moonshine</codename>
      <spins>
        <name>Live</name>
        <name>Fedora</name>
        <name>Everything</name>
      </spins>
    </distribution>

    <distribution>
      <name>Fedora Core</name>
      <release>6</release>
      <codename>Zod</codename>
      <spins></spins>
    </distribution>
   </linux>
  </os>    
</xml>

在 XML 文件中查找数据时,你的第一个任务是关注要探索的节点。如果知道节点的路径,请使用 -value of 选项指定完整路径。你越早浏览 文档对象模型(DOM)树,就可以看到更多信息:

$ xmlstarlet select --template \
  --value-of /xml/os/linux/distribution \
  --nl myfile.xml
      Fedora
      7
      Moonshine
     
        Live
        Fedora
        Everything     
     
      Fedora Core
      6
      Zod

--nl 代表“新的一行”,它插入大量的空白,以确保在输入结果后,终端在新的一行显示。我已经删除了样本输出中的一些多余空间。

通过进一步深入 DOM 树来凝聚关注点:

$ xmlstarlet select --template \
  --value-of /xml/os/linux/distribution/name \
  --nl myfile.xml
Fedora
Fedora Core

条件选择

用于导航和解析 XML 的最强大工具之一被称为 XPath。它规范了 XML 搜索中使用的语法,并从 XML 库调用函数。XMLStarlet 能够解析 XPath 表达式,因此可以使用 XPath 函数来有条件的进行选择。XPath 具有丰富的函数,由 W3C 提供了详细文档,但我觉得 Mozilla 的 XPath 文档 更简洁。

可以使用方括号作为测试函数,将元素的内容与某个值进行比较。下面是对 <name> 元素的值的测试,它仅返回与特定匹配相关联的版本号。

想象一下,示例 XML 文件包含以 1 开头的所有 Fedora 版本。要查看与旧名称 “Fedora Core” 关联的所有版本号(该项目从版本 7 开始删除了名称中的 “Core”),请执行以下操作:

$ xmlstarlet sel --template \
  --value-of '/xml/os/linux/distribution[name = "Fedora Core"]/release' \
  --nl myfile.xml
6
5
4
3
2
1

通过将路径的 --value-of 更改为 /xml/os/linux/distribution[name=“Fedora Core”]/codename,你便可以查看这些版本的所有代号。

匹配路径和获取目标值

将 XML 标记视为节点的一个好处是,一旦找到节点,就可以将其视为当前的数据的“目录”。它不是一个真正的目录,至少不是文件系统意义上的目录,但它是一个可以查询的数据集合。为了帮助你将目标和“里面”的数据分开,XMLStarlet 把你试图用 --match 选项匹配的内容和用 --value-of 选项匹配的数据值进行了区分。

假设你知道 <spin> 节点包含几个元素。这就是你的目标节点。一旦到了这里,就可以使用 --value-of 指定想要哪个元素的值。要查看所有元素,可以使用点(.)来代表当前位置:

$ xmlstarlet sel --template \
  --match '/xml/os/linux/distribution/spin' \
  --value-of '.' --nl myfile.xml \
Live
Fedora
Everything

与浏览 DOM 一样,可以使用 XPath 表达式来限制返回数据的范围。在本例中,我使用 last() 函数来检索 spin 节点中的最后一个元素:

$ xmlstarlet select --template \
  --match '/xml/os/linux/distribution/spin' \
  --value-of '*[last()]' --nl myfile.xml
Everything

在本例中,我使用 position() 函数选择 spin 节点中的特定元素:

$ xmlstarlet select --template \
  --match '/xml/os/linux/distribution/spin' \
  --value-of '*[position() = 2]' --nl myfile.xml
Fedora

--match--value 选项可以重叠,因此如何将它们一起使用取决于你自己。对于示例 XML,这两个表达式执行的是相同的操作:

$ xmlstarlet select  --template \
  --match '/xml/os/linux/distribution/spin' \
  --value-of '.' \
  --nl myfile.xml
Live
Fedora
Everything     

$ xmlstarlet select --template \
  --match '/xml/os/linux/distribution' \
  --value-of 'spin' \
  --nl myfile.xml
Live
Fedora
Everything

熟悉 XML

XML 有时看起来过于冗长和笨拙,但为与之交互和构建的工具却总是让我吃惊。如果你想要好好使用 XML,那么 XMLStarlet 可能是一个很好的切入点。下次要打开 XML 文件查看其结构化数据时,请尝试使用 XMLStarlet,看看是否可以查询这些数据。当你对 XML 越熟悉时,它就越能作为一种健壮灵活的数据格式而为你服务。


via: https://opensource.com/article/21/7/parse-xml-linux

作者:Seth Kenlon 选题:lujun9972 译者:zepoch 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

了解一下 XML 吧,它是一种严格但灵活的标记语言,无论是在文档还是图像方面应用都十分广泛。

 title=

XML 是一种分层的标记语言。它使用打开和关闭标签来定义数据,它常用来存储和交换数据,而且由于它极大的灵活性,不论是在 文档 还是 图像 中都用的非常多。

这里是一个 XML 文档的例子:

<xml>
  <os>
   <linux>
    <distribution>
      <name>Fedora</name>
      <release>8</release>
      <codename>Werewolf</codename>
    </distribution>

    <distribution>
      <name>Slackware</name>
      <release>12.1</release>
      <mascot>
        <official>Tux</official>
        <unofficial>Bob Dobbs</unofficial>
      </mascot>
    </distribution>
   </linux>
  </os>    
</xml>

阅读这个示例 XML,你可能会发现这个 XML 的格式具有直观的特性。 无论你是否熟悉这个文档的主题,你都可能理解本文档中的数据。 这部分原因是因为 XML 被认为是冗长的。 它使用了很多标签,标签可以有很长的描述性名称,并且数据以分层的方式排序,这有助于解释数据之间的关系。 你可能从这个示例中了解到 Fedora 发行版和 Slackware 发行版是两个不同且不相关的 Linux 发行版,因为每个实例都“包含”在自己独立的 <distribution> 标签中。

XML 也非常灵活。 与 HTML 不同,它没有预定义的标签列表。 你可以自由创建你需要表示任何数据结构的标签。

XML 的组成

数据的存在为了读取,当计算机“读取”数据时,该过程称为 解析。 再次使用示例 XML 数据,以下是大多数 XML 解析器认为重要的术语。

  • 文档<xml> 标签标记文档的开始, </xml> 标签标记文档的结束。
  • 节点<os><distribution><mascot> 这些都是节点,在解析术语中,节点是包含其他标签的标签。
  • 元素:像 <name>Fedora</name><official>Tux</official> 这些都是元素。从第一个<开始,到最后一个 > 结束是一个元素。
  • 内容:在两个元素标签之间的数据被称之为内容,在第一个 <name> 标签中,Fedora 字符串就是一个内容。

XML 模式

XML 文档中的标签和标签继承性称为 模式

一些模式是随意组成的(例如,本文中的示例 XML 代码纯粹是即兴创作的),而其他模式则由标准组织严格定义。 例如,可缩放矢量图形(SVG)模式 由 W3C 定义,而 DocBook 模式 由 Norman Walsh 定义。

模式强制执行一致性。 最基本的模式通常也是最严格的。 在我的示例 XML 代码中,将发行版名称放在 <mascot> 节点中是没有意义的,因为文档的隐含模式清楚地表明 mascot 必须是发行版的“子”元素。

数据对象模型(DOM)

如果你必须不断地描述标签和位置(例如,“系统部分中的 Linux 部分中第二个发行版标签的名称标签”),那么谈论 XML 会让人感到困惑,因此解析器使用文档对象模型(DOM)的概念来表示 XML 数据。 DOM 将 XML 数据放入一种“家谱”结构中,从根元素(在我的示例 XML 中,即 os 标记)开始并包括路径上的每个标记。

 title=

这种相同的 XML 数据结构可以表示为路径,就像 Linux 系统中的文件或互联网上网页的位置一样。 例如,<mascot> 标签的路径可以表示为 //os/linux/distribution/slackware/mascot

两个 <distribution> 标签可以被表示为 //os/linux/distribution ,因为这里有两个发行版的节点,因此一个解析器可以直接将两个节点的内容载入到一个数组中,可以进行查询。

严格的 XML

XML 也以严格而著称。 这意味着大多数应用程序被设计为在遇到 XML 错误时就会故意失败。 这听起来可能有问题,但这是开发人员最欣赏 XML 的事情之一,因为当应用程序试图猜测如何解决错误时,可能会发生不可预测的事情。 例如,在 HTML 定义明确之前,大多数 Web 浏览器都包含“怪癖模式”,因此当人们试图查看糟糕的 HTML 代码时,Web 浏览器却可以加载作者可能想要的内容。 结果非常难以预测,尤其是当一个浏览器的猜测与另一个浏览器不同时。

XML 通过在出现故意错误时失败而不允许这样做。 这让作者可以修复错误,直到它们生成有效的 XML。 因为 XML 是良好定义的,所以有许多应用程序的验证器插件以及像 xmllintxmlstarlet 这样的独立命令来帮助你及早定位错误。

转换 XML

因为 XML 通常用作数据交换,所以将 XML 转换为其他数据格式或其他 XML 模式是很常见的。 经典示例包括 XSLTProc、xmlto 和 pandoc,但从技术上讲,还有许多其他应用程序或者至少程序的一部分就是在转换 XML。

事实上,LibreOffice 使用 XML 来布局其文字处理器和电子表格文档,因此无论何时你导出或 从 LibreOffice 转换文件,你都在转换 XML。

开源 EPUB 格式的电子书 使用 XML,因此无论何时你 将文档转换为 EPUB 或从 EPUB 转换,你都在转换 XML。

Inkscape 是基于矢量的插图应用程序,它将其文件保存在 SVG 中,这是一种专为图形设计的 XML 模式。 任何时候你将 Inkscape 中的图像导出为 PNG 文件时,你都在转换 XML。

名单还可以一直继续下去。 XML 是一种数据存储格式,旨在确保你的数据,无论是画布上的点和线、图表上的节点,还是文档中的文字,都可以轻松准确地提取、更新和转换。

学习 XML

编写 XML 很像编写 HTML。 感谢 Jay Nick 的辛勤工作,在线提供免费且有趣的 XML 课程 可以教你如何使用 XML 创建图形。

通常,探索 XML 所需的特殊工具很少。 由于 HTML 和 XML 之间的密切关系,你可以 使用 Web 浏览器查看 XML。 此外,QXmlEditNetBeansKate 等开源文本编辑器通过有用的提示、自动完成、语法验证等,使键入和阅读 XML 变得容易。

选择 XML

XML 起初可能看起来有很多数据,但它与 HTML 并没有太大的不同(实际上,HTML 已经 以 XHTML 的形式重新实现为 XML)。 XML 有一个独特的好处,即构成其结构的标签也恰好是元数据,提供有关其存储内容的信息。 精心设计的 XML 模式包含并描述你的数据,使用户能够一目了然并快速解析它,并使开发人员能够使用一些库 快速解析


via: https://opensource.com/article/21/7/what-xml

作者:Seth Kenlon 选题:lujun9972 译者:amwps290 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在以不同语言编写并在不同平台上运行的应用程序之间交换数据时,Protobuf 编码可提高效率。

协议缓冲区 Protocol Buffers Protobufs)像 XML 和 JSON 一样,可以让用不同语言编写并在不同平台上运行的应用程序交换数据。例如,用 Go 编写的发送程序可以在 Protobuf 中对以 Go 表示的销售订单数据进行编码,然后用 Java 编写的接收方可以对它进行解码,以获取所接收订单数据的 Java 表示方式。这是在网络连接上的结构示意图:

Go 销售订单 —> Pbuf 编码 —> 网络 —> Pbuf 界面 —> Java 销售订单

与 XML 和 JSON 相比,Protobuf 编码是二进制而不是文本,这会使调试复杂化。但是,正如本文中的代码示例所确认的那样,Protobuf 编码在大小上比 XML 或 JSON 编码要有效得多。

Protobuf 以另一种方式提供了这种有效性。在实现级别,Protobuf 和其他编码系统对结构化数据进行 序列化 serialize 反序列化 deserialize 。序列化将特定语言的数据结构转换为字节流,反序列化是将字节流转换回特定语言的数据结构的逆运算。序列化和反序列化可能成为数据交换的瓶颈,因为这些操作会占用大量 CPU。高效的序列化和反序列化是 Protobuf 的另一个设计目标。

最近的编码技术,例如 Protobuf 和 FlatBuffers,源自 1990 年代初期的 DCE/RPC 分布式计算环境/远程过程调用 Distributed Computing Environment/Remote Procedure Call )计划。与 DCE/RPC 一样,Protobuf 在数据交换中为 IDL(接口定义语言)和编码层做出了贡献。

本文将着眼于这两层,然后提供 Go 和 Java 中的代码示例以充实 Protobuf 的细节,并表明 Protobuf 是易于使用的。

Protobuf 作为一个 IDL 和编码层

像 Protobuf 一样,DCE/RPC 被设计为与语言和平台无关。适当的库和实用程序允许任何语言和平台用于 DCE/RPC 领域。此外,DCE/RPC 体系结构非常优雅。IDL 文档是一侧的远程过程与另一侧的调用者之间的协定。Protobuf 也是以 IDL 文档为中心的。

IDL 文档是文本,在 DCE/RPC 中,使用基本 C 语法以及元数据的语法扩展(方括号)和一些新的关键字,例如 interface。这是一个例子:

[uuid (2d6ead46-05e3-11ca-7dd1-426909beabcd), version(1.0)]
interface echo {
   const long int ECHO_SIZE = 512;
   void echo(
      [in]          handle_t h,
      [in, string]  idl_char from_client[ ],
      [out, string] idl_char from_service[ECHO_SIZE]
   );
}

该 IDL 文档声明了一个名为 echo 的过程,该过程带有三个参数:类型为 handle_t(实现指针)和 idl_char(ASCII 字符数组)的 [in] 参数被传递给远程过程,而 [out] 参数(也是一个字符串)从该过程中传回。在此示例中,echo 过程不会显式返回值(echo 左侧的 void),但也可以返回值。返回值,以及一个或多个 [out] 参数,允许远程过程任意返回许多值。下一节将介绍 Protobuf IDL,它的语法不同,但同样用作数据交换中的协定。

DCE/RPC 和 Protobuf 中的 IDL 文档是创建用于交换数据的基础结构代码的实用程序的输入:

IDL 文档 —> DCE/PRC 或 Protobuf 实用程序 —> 数据交换的支持代码

作为相对简单的文本,IDL 是同样便于人类阅读的关于数据交换细节的文档(特别是交换的数据项的数量和每个项的数据类型)。

Protobuf 可用于现代 RPC 系统,例如 gRPC;但是 Protobuf 本身仅提供 IDL 层和编码层,用于从发送者传递到接收者的消息。与原本的 DCE/RPC 一样,Protobuf 编码是二进制的,但效率更高。

目前,XML 和 JSON 编码仍在通过 Web 服务等技术进行的数据交换中占主导地位,这些技术利用 Web 服务器、传输协议(例如 TCP、HTTP)以及标准库和实用程序等原有的基础设施来处理 XML 和 JSON 文档。 此外,各种类型的数据库系统可以存储 XML 和 JSON 文档,甚至旧式关系型系统也可以轻松生成查询结果的 XML 编码。现在,每种通用编程语言都具有支持 XML 和 JSON 的库。那么,是什么让我们回到 Protobuf 之类的二进制编码系统呢?

让我们看一下负十进制值 -128。以 2 的补码二进制表示形式(在系统和语言中占主导地位)中,此值可以存储在单个 8 位字节中:10000000。此整数值在 XML 或 JSON 中的文本编码需要多个字节。例如,UTF-8 编码需要四个字节的字符串,即 -128,即每个字符一个字节(十六进制,值为 0x2d0x310x320x38)。XML 和 JSON 还添加了标记字符,例如尖括号和大括号。有关 Protobuf 编码的详细信息下面就会介绍,但现在的关注点是一个通用点:文本编码的压缩性明显低于二进制编码。

在 Go 中使用 Protobuf 的示例

我的代码示例着重于 Protobuf 而不是 RPC。以下是第一个示例的概述:

  • 名为 dataitem.proto 的 IDL 文件定义了一个 Protobuf 消息,它具有六个不同类型的字段:具有不同范围的整数值、固定大小的浮点值以及两个不同长度的字符串。
  • Protobuf 编译器使用 IDL 文件生成 Go 版本(以及后面的 Java 版本)的 Protobuf 消息及支持函数。
  • Go 应用程序使用随机生成的值填充原生的 Go 数据结构,然后将结果序列化为本地文件。为了进行比较, XML 和 JSON 编码也被序列化为本地文件。
  • 作为测试,Go 应用程序通过反序列化 Protobuf 文件的内容来重建其原生数据结构的实例。
  • 作为语言中立性测试,Java 应用程序还会对 Protobuf 文件的内容进行反序列化以获取原生数据结构的实例。

我的网站上提供了该 IDL 文件以及两个 Go 和一个 Java 源文件,打包为 ZIP 文件。

最重要的 Protobuf IDL 文档如下所示。该文档存储在文件 dataitem.proto 中,并具有常规的.proto 扩展名。

示例 1、Protobuf IDL 文档

syntax = "proto3";

package main;

message DataItem {
  int64  oddA  = 1;
  int64  evenA = 2;
  int32  oddB  = 3;
  int32  evenB = 4;
  float  small = 5;
  float  big   = 6;
  string short = 7;
  string long  = 8;
}

该 IDL 使用当前的 proto3 而不是较早的 proto2 语法。软件包名称(在本例中为 main)是可选的,但是惯例使用它以避免名称冲突。这个结构化的消息包含八个字段,每个字段都有一个 Protobuf 数据类型(例如,int64string)、名称(例如,oddAshort)和一个等号 = 之后的数字标签(即键)。标签(在此示例中为 1 到 8)是唯一的整数标识符,用于确定字段序列化的顺序。

Protobuf 消息可以嵌套到任意级别,而一个消息可以是另外一个消息的字段类型。这是一个使用 DataItem 消息作为字段类型的示例:

message DataItems {
  repeated DataItem item = 1;
}

单个 DataItems 消息由重复的(零个或多个)DataItem 消息组成。

为了清晰起见,Protobuf 还支持枚举类型:

enum PartnershipStatus {
  reserved "FREE", "CONSTRAINED", "OTHER";
}

reserved 限定符确保用于实现这三个符号名的数值不能重复使用。

为了生成一个或多个声明 Protobuf 消息结构的特定于语言的版本,包含这些结构的 IDL 文件被传递到protoc 编译器(可在 Protobuf GitHub 存储库中找到)。对于 Go 代码,可以以通常的方式安装支持的 Protobuf 库(这里以 作为命令行提示符):

% go get github.com/golang/protobuf/proto

将 Protobuf IDL 文件 dataitem.proto 编译为 Go 源代码的命令是:

% protoc --go_out=. dataitem.proto

标志 --go_out 指示编译器生成 Go 源代码。其他语言也有类似的标志。在这种情况下,结果是一个名为 dataitem.pb.go 的文件,该文件足够小,可以将其基本内容复制到 Go 应用程序中。以下是生成的代码的主要部分:

var _ = proto.Marshal

type DataItem struct {
   OddA  int64   `protobuf:"varint,1,opt,name=oddA" json:"oddA,omitempty"`
   EvenA int64   `protobuf:"varint,2,opt,name=evenA" json:"evenA,omitempty"`
   OddB  int32   `protobuf:"varint,3,opt,name=oddB" json:"oddB,omitempty"`
   EvenB int32   `protobuf:"varint,4,opt,name=evenB" json:"evenB,omitempty"`
   Small float32 `protobuf:"fixed32,5,opt,name=small" json:"small,omitempty"`
   Big   float32 `protobuf:"fixed32,6,opt,name=big" json:"big,omitempty"`
   Short string  `protobuf:"bytes,7,opt,name=short" json:"short,omitempty"`
   Long  string  `protobuf:"bytes,8,opt,name=long" json:"long,omitempty"`
}

func (m *DataItem) Reset()         { *m = DataItem{} }
func (m *DataItem) String() string { return proto.CompactTextString(m) }
func (*DataItem) ProtoMessage()    {}
func init() {}

编译器生成的代码具有 Go 结构 DataItem,该结构导出 Go 字段(名称现已大写开头),该字段与 Protobuf IDL 中声明的名称匹配。该结构字段具有标准的 Go 数据类型:int32int64float32string。在每个字段行的末尾,是描述 Protobuf 类型的字符串,提供 Protobuf IDL 文档中的数字标签及有关 JSON 信息的元数据,这将在后面讨论。

此外也有函数;最重要的是 Proto.Marshal,用于将 DataItem 结构的实例序列化为 Protobuf 格式。辅助函数包括:清除 DataItem 结构的 Reset,生成 DataItem 的单行字符串表示的 String

描述 Protobuf 编码的元数据应在更详细地分析 Go 程序之前进行仔细研究。

Protobuf 编码

Protobuf 消息的结构为键/值对的集合,其中数字标签为键,相应的字段为值。字段名称(例如,oddAsmall)是供人类阅读的,但是 protoc 编译器的确使用了字段名称来生成特定于语言的对应名称。例如,Protobuf IDL 中的 oddAsmall 名称在 Go 结构中分别成为字段 OddASmall

键和它们的值都被编码,但是有一个重要的区别:一些数字值具有固定大小的 32 或 64 位的编码,而其他数字(包括消息标签)则是 varint 编码的,位数取决于整数的绝对值。例如,整数值 1 到 15 需要 8 位 varint 编码,而值 16 到 2047 需要 16 位。varint 编码在本质上与 UTF-8 编码类似(但细节不同),它偏爱较小的整数值而不是较大的整数值。(有关详细分析,请参见 Protobuf 编码指南)结果是,Protobuf 消息应该在字段中具有较小的整数值(如果可能),并且键数应尽可能少,但每个字段至少得有一个键。

下表 1 列出了 Protobuf 编码的要点:

编码示例类型长度
varintint32uint32int64可变长度
fixedfixed32floatdouble固定的 32 位或 64 位长度
字节序列stringbytes序列长度

表 1. Protobuf 数据类型

未明确固定长度的整数类型是 varint 编码的;因此,在 varint 类型中,例如 uint32u 代表无符号),数字 32 描述了整数的范围(在这种情况下为 0 到 2 32 - 1),而不是其位的大小,该位大小取决于值。相比之下,对于固定长度类型(例如 fixed32double),Protobuf 编码分别需要 32 位和 64 位。Protobuf 中的字符串是字节序列;因此,字段编码的大小就是字节序列的长度。

另一个高效的方法值得一提。回想一下前面的示例,其中的 DataItems 消息由重复的 DataItem 实例组成:

message DataItems {
  repeated DataItem item = 1;
}

repeated 表示 DataItem 实例是打包的:集合具有单个标签,在这里是 1。因此,具有重复的 DataItem 实例的 DataItems 消息比具有多个但单独的 DataItem 字段、每个字段都需要自己的标签的消息的效率更高。

了解了这一背景,让我们回到 Go 程序。

dataItem 程序的细节

dataItem 程序创建一个 DataItem 实例,并使用适当类型的随机生成的值填充字段。Go 有一个 rand 包,带有用于生成伪随机整数和浮点值的函数,而我的 randString 函数可以从字符集中生成指定长度的伪随机字符串。设计目标是要有一个具有不同类型和位大小的字段值的 DataItem 实例。例如,OddAEvenA 值分别是 64 位非负整数值的奇数和偶数;但是 OddBEvenB 变体的大小为 32 位,并存放 0 到 2047 之间的小整数值。随机浮点值的大小为 32 位,字符串为 16(Short)和 32(Long)字符的长度。这是用随机值填充 DataItem 结构的代码段:

// 可变长度整数
n1 := rand.Int63()        // 大整数
if (n1 & 1) == 0 { n1++ } // 确保其是奇数
...
n3 := rand.Int31() % UpperBound // 小整数
if (n3 & 1) == 0 { n3++ }       // 确保其是奇数

// 固定长度浮点数
...
t1 := rand.Float32()
t2 := rand.Float32()
...
// 字符串
str1 := randString(StrShort)
str2 := randString(StrLong)

// 消息
dataItem := &DataItem {
   OddA:  n1,
   EvenA: n2,
   OddB:  n3,
   EvenB: n4,
   Big:   f1,
   Small: f2,
   Short: str1,
   Long:  str2,
}

创建并填充值后,DataItem 实例将以 XML、JSON 和 Protobuf 进行编码,每种编码均写入本地文件:

func encodeAndserialize(dataItem *DataItem) {
   bytes, _ := xml.MarshalIndent(dataItem, "", " ")  // Xml to dataitem.xml
   ioutil.WriteFile(XmlFile, bytes, 0644)            // 0644 is file access permissions

   bytes, _ = json.MarshalIndent(dataItem, "", " ")  // Json to dataitem.json
   ioutil.WriteFile(JsonFile, bytes, 0644)

   bytes, _ = proto.Marshal(dataItem)                // Protobuf to dataitem.pbuf
   ioutil.WriteFile(PbufFile, bytes, 0644)
}

这三个序列化函数使用术语 marshal,它与 serialize 意思大致相同。如代码所示,三个 Marshal 函数均返回一个字节数组,然后将其写入文件。(为简单起见,忽略可能的错误处理。)在示例运行中,文件大小为:

dataitem.xml:  262 bytes
dataitem.json: 212 bytes
dataitem.pbuf:  88 bytes

Protobuf 编码明显小于其他两个编码方案。通过消除缩进字符(在这种情况下为空白和换行符),可以稍微减小 XML 和 JSON 序列化的大小。

以下是 dataitem.json 文件,该文件最终是由 json.MarshalIndent 调用产生的,并添加了以 ## 开头的注释:

{
 "oddA":  4744002665212642479,                ## 64-bit >= 0
 "evenA": 2395006495604861128,                ## ditto
 "oddB":  57,                                 ## 32-bit >= 0 but < 2048
 "evenB": 468,                                ## ditto
 "small": 0.7562016,                          ## 32-bit floating-point
 "big":   0.85202795,                         ## ditto
 "short": "ClH1oDaTtoX$HBN5",                 ## 16 random chars
 "long":  "xId0rD3Cri%3Wt%^QjcFLJgyXBu9^DZI"  ## 32 random chars
}

尽管这些序列化的数据写入到本地文件中,但是也可以使用相同的方法将数据写入网络连接的输出流。

测试序列化和反序列化

Go 程序接下来通过将先前写入 dataitem.pbuf 文件的字节反序列化为 DataItem 实例来运行基本测试。这是代码段,其中去除了错误检查部分:

filebytes, err := ioutil.ReadFile(PbufFile) // get the bytes from the file
...
testItem.Reset()                            // clear the DataItem structure
err = proto.Unmarshal(filebytes, testItem)  // deserialize into a DataItem instance

用于 Protbuf 反序列化的 proto.Unmarshal 函数与 proto.Marshal 函数相反。原始的 DataItem 和反序列化的副本将被打印出来以确认完全匹配:

Original:
2041519981506242154 3041486079683013705 1192 1879
0.572123 0.326855
boPb#T0O8Xd&Ps5EnSZqDg4Qztvo7IIs 9vH66AiGSQgCDxk&

Deserialized:
2041519981506242154 3041486079683013705 1192 1879
0.572123 0.326855
boPb#T0O8Xd&Ps5EnSZqDg4Qztvo7IIs 9vH66AiGSQgCDxk&

一个 Java Protobuf 客户端

用 Java 写的示例是为了确认 Protobuf 的语言中立性。原始 IDL 文件可用于生成 Java 支持代码,其中涉及嵌套类。但是,为了抑制警告信息,可以进行一些补充。这是修订版,它指定了一个 DataMsg 作为外部类的名称,内部类在该 Protobuf 消息后面自动命名为 DataItem

syntax = "proto3";

package main;

option java_outer_classname = "DataMsg";

message DataItem {
...

进行此更改后,protoc 编译与以前相同,只是所期望的输出现在是 Java 而不是 Go:

% protoc --java_out=. dataitem.proto

生成的源文件(在名为 main 的子目录中)为 DataMsg.java,长度约为 1,120 行:Java 并不简洁。编译然后运行 Java 代码需要具有 Protobuf 库支持的 JAR 文件。该文件位于 Maven 存储库中。

放置好这些片段后,我的测试代码相对较短(并且在 ZIP 文件中以 Main.java 形式提供):

package main;
import java.io.FileInputStream;

public class Main {
   public static void main(String[] args) {
      String path = "dataitem.pbuf";  // from the Go program's serialization
      try {
         DataMsg.DataItem deserial =
           DataMsg.DataItem.newBuilder().mergeFrom(new FileInputStream(path)).build();

         System.out.println(deserial.getOddA()); // 64-bit odd
         System.out.println(deserial.getLong()); // 32-character string
      }
      catch(Exception e) { System.err.println(e); }
    }
}

当然,生产级的测试将更加彻底,但是即使是该初步测试也可以证明 Protobuf 的语言中立性:dataitem.pbuf 文件是 Go 程序对 Go 语言版的 DataItem 进行序列化的结果,并且该文件中的字节被反序列化以产生一个 Java 语言的 DataItem 实例。Java 测试的输出与 Go 测试的输出相同。

用 numPairs 程序来结束

让我们以一个示例作为结尾,来突出 Protobuf 效率,但又强调在任何编码技术中都会涉及到的成本。考虑以下 Protobuf IDL 文件:

syntax = "proto3";
package main;

message NumPairs {
  repeated NumPair pair = 1;
}

message NumPair {
  int32 odd = 1;
  int32 even = 2;
}

NumPair 消息由两个 int32 值以及每个字段的整数标签组成。NumPairs 消息是嵌入的 NumPair 消息的序列。

Go 语言的 numPairs 程序(如下)创建了 200 万个 NumPair 实例,每个实例都附加到 NumPairs 消息中。该消息可以按常规方式进行序列化和反序列化。

示例 2、numPairs 程序

package main

import (
   "math/rand"
   "time"
   "encoding/xml"
   "encoding/json"
   "io/ioutil"
   "github.com/golang/protobuf/proto"
)

// protoc-generated code: start
var _ = proto.Marshal
type NumPairs struct {
   Pair []*NumPair `protobuf:"bytes,1,rep,name=pair" json:"pair,omitempty"`
}

func (m *NumPairs) Reset()         { *m = NumPairs{} }
func (m *NumPairs) String() string { return proto.CompactTextString(m) }
func (*NumPairs) ProtoMessage()    {}
func (m *NumPairs) GetPair() []*NumPair {
   if m != nil { return m.Pair }
   return nil
}

type NumPair struct {
   Odd  int32 `protobuf:"varint,1,opt,name=odd" json:"odd,omitempty"`
   Even int32 `protobuf:"varint,2,opt,name=even" json:"even,omitempty"`
}

func (m *NumPair) Reset()         { *m = NumPair{} }
func (m *NumPair) String() string { return proto.CompactTextString(m) }
func (*NumPair) ProtoMessage()    {}
func init() {}
// protoc-generated code: finish

var numPairsStruct NumPairs
var numPairs = &numPairsStruct

func encodeAndserialize() {
   // XML encoding
   filename := "./pairs.xml"
   bytes, _ := xml.MarshalIndent(numPairs, "", " ")
   ioutil.WriteFile(filename, bytes, 0644)

   // JSON encoding
   filename = "./pairs.json"
   bytes, _ = json.MarshalIndent(numPairs, "", " ")
   ioutil.WriteFile(filename, bytes, 0644)

   // ProtoBuf encoding
   filename = "./pairs.pbuf"
   bytes, _ = proto.Marshal(numPairs)
   ioutil.WriteFile(filename, bytes, 0644)
}

const HowMany = 200 * 100  * 100 // two million

func main() {
   rand.Seed(time.Now().UnixNano())

   // uncomment the modulus operations to get the more efficient version
   for i := 0; i < HowMany; i++ {
      n1 := rand.Int31() // % 2047
      if (n1 & 1) == 0 { n1++ } // ensure it's odd
      n2 := rand.Int31() // % 2047
      if (n2 & 1) == 1 { n2++ } // ensure it's even

      next := &NumPair {
                 Odd:  n1,
                 Even: n2,
              }
      numPairs.Pair = append(numPairs.Pair, next)
   }
   encodeAndserialize()
}

每个 NumPair 中随机生成的奇数和偶数值的范围在 0 到 20 亿之间变化。就原始数据(而非编码数据)而言,Go 程序中生成的整数总共为 16MB:每个 NumPair 为两个整数,总计为 400 万个整数,每个值的大小为四个字节。

为了进行比较,下表列出了 XML、JSON 和 Protobuf 编码的示例 NumsPairs 消息的 200 万个 NumPair 实例。原始数据也包括在内。由于 numPairs 程序生成随机值,因此样本运行的输出有所不同,但接近表中显示的大小。

编码文件字节大小Pbuf/其它 比例
pairs.raw16MB169%
Protobufpairs.pbuf27MB
JSONpairs.json100MB27%
XMLpairs.xml126MB21%

表 2. 16MB 整数的编码开销

不出所料,Protobuf 和之后的 XML 和 JSON 差别明显。Protobuf 编码大约是 JSON 的四分之一,是 XML 的五分之一。但是原始数据清楚地表明 Protobuf 也会产生编码开销:序列化的 Protobuf 消息比原始数据大 11MB。包括 Protobuf 在内的任何编码都涉及结构化数据,这不可避免地会增加字节。

序列化的 200 万个 NumPair 实例中的每个实例都包含个整数值:Go 结构中的 EvenOdd 字段分别一个,而 Protobuf 编码中的每个字段、每个标签一个。对于原始数据(而不是编码数据),每个实例将达到 16 个字节,样本 NumPairs 消息中有 200 万个实例。但是 Protobuf 标记(如 NumPair 字段中的 int32 值)使用 varint 编码,因此字节长度有所不同。特别是,小的整数值(在这种情况下,包括标签在内)需要不到四个字节进行编码。

如果对 numPairs 程序进行了修改,以使两个 NumPair 字段的值小于 2048,且其编码为一或两个字节,则 Protobuf 编码将从 27MB 下降到 16MB,这正是原始数据的大小。下表总结了样本运行中的新编码大小。

编码文件字节大小Pbuf/其它 比例
Nonepairs.raw16MB100%
Protobufpairs.pbuf16MB
JSONpairs.json77MB21%
XMLpairs.xml103MB15%

表 3. 编码 16MB 的小于 2048 的整数

总之,修改后的 numPairs 程序的字段值小于 2048,可减少原始数据中每个四字节整数值的大小。但是 Protobuf 编码仍然需要标签,这些标签会在 Protobuf 消息中添加字节。Protobuf 编码确实会增加消息大小,但是如果要编码相对较小的整数值(无论是字段还是键),则可以通过 varint 因子来减少此开销。

对于包含混合类型的结构化数据(且整数值相对较小)的中等大小的消息,Protobuf 明显优于 XML 和 JSON 等选项。在其他情况下,数据可能不适合 Protobuf 编码。例如,如果两个应用程序需要共享大量文本记录或大整数值,则可以采用压缩而不是编码技术。


via: https://opensource.com/article/19/10/protobuf-data-interchange

作者:Marty Kalin 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

一段超级严肃的关于样本序列化的集合、子集和超集的文字

我是一名开发者,我读代码,我写代码,我写会写代码的代码,我写会写出供其它代码读的代码的代码。这些都非常火星语,但是有其美妙之处。然而,最后一点,写会写出供其它代码读的代码的代码,可以很快变得比这段文字更费解。有很多方法可以做到这一点。一种不那么复杂而且开发者社区最爱的方式是数据序列化。对于那些不了解我刚刚抛给你的时髦词的人,数据序列化是从一个系统获取一些信息,将其转换为其它系统可以读取的格式,然后将其传递给其它系统的过程。

虽然数据序列化格式多到可以埋葬哈利法塔,但它们大多分为两类:

  • 易于人类读写,
  • 易于机器读写。

很难两全其美,因为人类喜欢让我们更具表现力的松散类型和灵活格式标准,而机器倾向于被确切告知一切事情而没有二义性和细节缺失,并且认为“严格规范”才是它们最爱的口味。

由于我是一名 web 开发者,而且我们是一个创建网站的机构,我们将坚持使用 web 系统可以理解或不需要太多努力就能理解的特殊格式,而且对人类可读性特别有用的格式:XML、JSON、TOML、CSON 以及 YAML。每个都有各自的优缺点和适当的用例场景。

事实最先

回到互联网的早期,一些非常聪明的家伙决定整合一种让每个系统都能理解的标准语言,并创造性地将其命名为 标准通用标记语言 Standard Generalized Markup Language (简称 SGML)。SGML 非常灵活,发布者也很好地定义了它。它成为了 XML、SVG 和 HTML 等语言之父。所有这三个都符合 SGML 规范,可是它们都是规则更严格、灵活性更少的子集。

最终,人们开始看到非常小、简洁、易读且易于生成的数据的好处,这些数据可以在系统之间以编程的方式共享,而开销很小。大约在那个时候,JSON 诞生了并且能够满足所有的需求。而另一方面,其它语言也开始出现以处理更多的专业用例,如 CSON,TOML 和 YAML。

XML:不行了

原本,XML 语言非常灵活且易于编写,但它的缺点是冗长,人类难以阅读、计算机非常难以读取,并且有很多语法对于传达信息并不是完全必要的。

今天,它在 web 上的数据序列化的用途已经消失了。除非你在编写 HTML 或者 SVG,否则你不太能在许多其它地方看到 XML。一些过时的系统今天仍在使用它,但是用它传递数据往往太重了。

我已经可以听到 XML 老爷爷开始在它们的石碑上乱写为什么 XML 是了不起的,所以我将提供一个小小的补充:XML 可以很容易地由系统和人读写。然而,真的,我的意思是荒谬的,很难创建一个可以规范的读取它的系统。这是一个简单美观的 XML 示例:

<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>

太棒了。易于阅读、理解、写入,也容易编码一个可以读写它的系统。但请考虑这个例子:

<!DOCTYPE r [ <!ENTITY y "a]>b"> ]>
<r>
<a b="&y;>" />
<![CDATA[[a>b <a>b <a]]>
<?x <a> <!-- <b> ?> c --> d
</r>

这上面是 100% 有效的 XML。几乎不可能阅读、理解或推理。编写可以使用和理解这个的代码将花费至少 36 根头发和 248 磅咖啡渣。我们没有那么多时间或咖啡,而且我们大多数老程序员们现在都是秃头。所以,让它活在我们的记忆里,就像 css hacksIE 6 浏览器真空管一样好了。

JSON:并列聚会

好吧,我们都同意,XML = 差劲。那么,好的替代品是什么? JavaScript 对象表示法 JavaScript Object Notation ,简称 JSON。JSON(读起来像 Jason 这个名字) 是 Brendan Eich 发明的,并且得到了伟大而强力的 JavaScript 意见领袖 Douglas Crockford 的推广。它现在几乎用在任何地方。这种格式很容易由人和机器编写,按规范中的严格规则解析也相当容易,并且灵活 —— 允许深层嵌套数据,支持所有的原始数据类型,及将集合解释为数组或对象。JSON 成为了将数据从一个系统传输到另一个系统的事实标准。几乎所有语言都有内置读写它的功能。

JSON语法很简单。方括号表示数组,花括号表示记录,由冒号分隔的两个值分别表示属性或“键”(在左边)、值(在右边)。所有键必须用双引号括起来:

  {
    "books": [
      {
        "id": "bk102",
        "author": "Crockford, Douglas",
        "title": "JavaScript: The Good Parts",
        "genre": "Computer",
        "price": 29.99,
        "publish_date": "2008-05-01",
        "description": "Unearthing the Excellence in JavaScript"
      }
    ]
  }

这对你来说应该是完全有意义的。它简洁明了,并且从 XML 中删除了大量额外废话,并传达相同数量的信息。JSON 现在是王道,本文剩下的部分会介绍其它语言格式,这些格式只不过是 JSON 的简化版,尝试让其更简洁或对人类更易读,可结构还是非常相似的。

TOML: 缩短到彻底的利他主义

TOML( Tom 的显而易见的最小化语言 Tom’s Obvious, Minimal Language )允许以相当快捷、简洁的方式定义深层嵌套的数据结构。名字中的 Tom 是指发明者 Tom Preston Werner,他是一位活跃于我们行业的创造者和软件开发人员。与 JSON 相比,语法有点尴尬,更类似 ini 文件。这不是一个糟糕的语法,但是需要一些时间适应。

[[books]]
id = 'bk101'
author = 'Crockford, Douglas'
title = 'JavaScript: The Good Parts'
genre = 'Computer'
price = 29.99
publish_date = 2008-05-01T00:00:00+00:00
description = 'Unearthing the Excellence in JavaScript'

TOML 中集成了一些很棒的功能,例如多行字符串、保留字符的自动转义、日期、时间、整数、浮点数、科学记数法和“表扩展”等数据类型。最后一点是特别的,是 TOML 如此简洁的原因:

[a.b.c]
d = 'Hello'
e = 'World'

以上扩展到以下内容:

{
  "a": { 
    "b": {
      "c": { 
        "d": "Hello"
        "e": "World"
      }
    }
  }
}

使用 TOML,你可以肯定在时间和文件长度上会节省不少。很少有系统使用它或非常类似的东西作为配置,这是它最大的缺点。根本没有很多语言或库可以用来解释 TOML。

CSON: 特定系统所包含的简单样本

首先,有两个 CSON 规范。 一个代表 CoffeeScript Object Notation,另一个代表 Cursive Script Object Notation。后者不经常使用,所以我们不会关注它。我们只关注 CoffeeScript。

CSON 需要一点介绍。首先,我们来谈谈 CoffeeScript。CoffeeScript 是一种通过运行编译器生成 JavaScript 的语言。它允许你以更加简洁的语法编写 JavaScript 并转译成实际的 JavaScript,然后你可以在你的 web 应用程序中使用它。CoffeeScript 通过删除 JavaScript 中必需的许多额外语法,使编写 JavaScript 变得更容易。CoffeeScript 摆脱的一个大问题是花括号 —— 不需要它们。同样,CSON 是没有大括号的 JSON。它依赖于缩进来确定数据的层次结构。CSON 非常易于读写,并且通常比 JSON 需要更少的代码行,因为没有括号。

CSON 还提供一些 JSON 不提供的额外细节。多行字符串非常容易编写,你可以通过使用 # 符号开始一行来输入注释),并且不需要用逗号分隔键值对。

books: [
  id: 'bk102'
  author: 'Crockford, Douglas'
  title: 'JavaScript: The Good Parts'
  genre: 'Computer'
  price: 29.99
  publish_date: '2008-05-01'
  description: 'Unearthing the Excellence in JavaScript'
]

这是 CSON 的大问题。它是 CoffeScript 对象表示法 CoffeeScript Object Notation 。也就是说你要用 CoffeeScript 解析/标记化/lex/转译或其它方式来使用 CSON。CoffeeScript 是读取数据的系统。如果数据序列化的目的是允许数据从一个系统传递到另一个系统,这里我们有一个只能由单个系统读取的数据序列化格式,这使得它与防火火柴、防水海绵或者叉匙恼人的脆弱叉子部分一样有用。

如果这种格式被其它系统也采用,那它在开发者世界中可能非常有用。但到目前为止这基本上没有发生,所以在 PHP 或 JAVA 等替代语言中使用它是不行的。

YAML:年轻人的呼喊

开发人员感到高兴,因为 YAML 来自一个 Python 的贡献者。YAML 具有与 CSON 相同的功能集和类似的语法,有一系列新功能,以及几乎所有 web 编程语言都可用的解析器。它还有一些额外的功能,如循环引用、软包装、多行键、类型转换标签、二进制数据、对象合并和集合映射。它具有非常好的可读性和可写性,并且是 JSON 的超集,因此你可以在 YAML 中使用完全合格的 JSON 语法并且一切正常工作。你几乎不需要引号,它可以解释大多数基本数据类型(字符串、整数、浮点数、布尔值等)。

books:
  - id: bk102
  author: Crockford, Douglas
  title: 'JavaScript: The Good Parts'
  genre: Computer
  price: 29.99
  publish_date: !!str 2008-05-01
  description: Unearthing the Excellence in JavaScript

业界的年轻人正在迅速采用 YAML 作为他们首选的数据序列化和系统配置格式。他们这样做很机智。YAML 具有像 CSON 一样简洁的所有好处,以及与 JSON 一样的数据类型解释的所有功能。YAML 像加拿大人容易相处一样容易阅读。

YAML 有两个问题,对我而言,第一个是大问题。在撰写本文时,YAML 解析器尚未内置于多种语言,因此你需要使用第三方库或扩展来为你选择的语言解析 .yaml 文件。这不是什么大问题,可似乎大多数为 YAML 创建解析器的开发人员都选择随机将“附加功能”放入解析器中。有些允许标记化,有些允许链引用,有些甚至允许内联计算。这一切都很好(某种意义上),只是这些功能都不是规范的一部分,因此很难在其他语言的其他解析器中找到。这导致系统限定,你最终遇到了与 CSON 相同的问题。如果你使用仅在一个解析器中找到的功能,则其他解析器将无法解释输入。大多数这些功能都是无意义的,不属于数据集,而是属于你的应用程序逻辑,因此最好简单地忽略它们和编写符合规范的 YAML。

第二个问题是很少有解析器完全实现规范。所有的基本要素都有,但是很难找到一些更复杂和更新的东西,比如软包装、文档标记和首选语言的循环引用。我还没有看到对这些东西的刚需,所以希望它们不让你很失望。考虑到上述情况,我倾向于保持 1.1 规范 中呈现的更成熟的功能集,而避免在 1.2 规范 中找到的新东西。然而,编程是一个不断发展的怪兽,所以当你读完这篇文章时,你或许就可以使用 1.2 规范了。

最终哲学

这是最后一段话。每个序列化语言都应该以个案标准的方式评价。当涉及机器的可读性时,有些 无出其右 the bee’s knees 。对于人类可读性,有些 名至实归 the cat’s meow ,有些只是 金玉其外 gilded turds 。以下是最终细分:如果你要编写供其他代码阅读的代码,请使用 YAML。如果你正在编写能写出供其他代码读取的代码的代码,请使用 JSON。最后,如果你正在编写将代码转译为供其他代码读取的代码的代码,请重新考虑你的人生选择。


via: https://www.zionandzion.com/json-vs-xml-vs-toml-vs-cson-vs-yaml/

作者:Tim Anderson 选题:lujun9972 译者:GraveAccent 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

简介

XML 和 JSON 是现今互联网中最常用的两种数据交换格式。XML 格式由 W3C 于 1996 年提出。JSON 格式由 Douglas Crockford 于 2002 年提出。虽然这两种格式的设计目标并不相同,但它们常常用于同一个任务,也就是数据交换中。XML 和 JSON 的文档都很完善(RFC 7159RFC 4825),且都同时具有 人类可读性 human-readable 机器可读性 machine-readable 。这两种格式并没有哪一个比另一个更强,只是各自适用的领域不用。(LCTT 译注:W3C 是互联网联盟,制定了各种 Web 相关的标准,如 HTML、CSS 等。Douglas Crockford 除了制定了 JSON 格式,还致力于改进 JavaScript,开发了 JavaScript 相关工具 JSLintJSMin

XML 的优点

XML 与 JSON 相比有很多优点。二者间最大的不同在于 XML 可以通过在标签中添加属性这一简单的方法来存储 元数据 metadata 。而使用 JSON 时需要创建一个对象,把元数据当作对象的成员来存储。虽然二者都能达到存储元数据的目的,但在这一情况下 XML 往往是更好的选择,因为 JSON 的表达形式会让客户端程序开发人员误以为要将数据转换成一个对象。举个例子,如果你的 C++ 程序需要使用 JSON 格式发送一个附带元数据的整型数据,需要创建一个对象,用对象中的一个 名称/值对 name/value pair 来记录整型数据的值,再为每一个附带的属性添加一个名称/值对。接收到这个 JSON 的程序在读取后很可能把它当成一个对象,可事实并不是这样。虽然这是使用 JSON 传递元数据的一种变通方法,但他违背了 JSON 的核心理念:“ JSON 的结构与常规的程序语言中的结构相对应,而无需修改。 JSON’s structures look like conventional programming language structures. No restructuring is necessary. 1

虽然稍后我会说这也是 XML 的一个缺点,但 XML 中对命名冲突、 前缀 prefix 的处理机制赋予了它 JSON 所不具备的能力。程序员们可以通过前缀来把统一名称给予两个不同的实体。 2 当不同的实体在客户端中使用的名称相同时,这一特性会非常有用。

XML 的另一个优势在于大多数的浏览器可以把它以 具有高可读性和强组织性的方式 highly readable and organized way 展现给用户。XML 的树形结构让它易于结构化,浏览器也让用户可以自行展开或折叠树中的元素,这简直就是调试的福音。

XML 对比 JSON 有一个很重要的优势就是它可以记录 混合内容 mixed content 。例如在 XML 中处理包含结构化标记的字符串时,程序员们只要把带有标记的文本放在一个标签内就可以了。可因为 JSON 只包含数据,没有用于指明标签的简单方式,虽然可以使用处理元数据的解决方法,但这总有点滥用之嫌。

JSON 的优点

JSON 自身也有很多优点。其中最显而易见的一点就是 JSON 比 XML 简洁得多。因为 XML 中需要打开和关闭标签,而 JSON 使用名称/值对表示数据,使用简单的 {} 来标记对象,[] 来标记数组,, 来表示数据的分隔,: 表示名称和值的分隔。就算是使用 gzip 压缩,JSON 还是比 XML 要小,而且耗时更少。 3 正如 Sumaray 和 Makki 在实验中指出的那样,JSON 在很多方面都比 XML 更具优势,得出同样结果的还有 Nurseitov、Paulson、Reynolds 和 Izurieta。首先,由于 JSON 文件天生的简洁性,与包含相同信息的 XML 相比,JSON 总是更小,这意味着更快的传输和处理速度。第二,在不考虑大小的情况下,两组研究 4 5 表明使用 JSON 执行序列化和反序列化的速度显著优于使用 XML。第三,后续的研究指出 JSON 的处理在 CPU 资源的使用上也优于 XML。研究人员发现 JSON 在总体上使用的资源更少,其中更多的 CPU 资源消耗在用户空间,系统空间消耗的 CPU 资源较少。这一实验是在 RedHat 的设备上进行的,RedHat 表示更倾向于在用户空间使用 CPU 资源。 6 不出意外,Sumaray 和 Makki 在研究里还说明了在移动设备上 JSON 的性能也优于 XML。 7 这是有道理的,因为 JSON 消耗的资源更少,而移动设备的性能也更弱。

JSON 的另一个优点在于其对对象和数组的表述和 宿主语言 host language 中的数据结构相对应,例如 对象 object 记录 record 结构体 struct 字典 dictionary 哈希表 hash table 键值列表 keyed list 还有 数组 array 向量 vector 列表 list ,以及对象组成的数组等等。 8 虽然 XML 里也能表达这些数据结构,也只需调用一个函数就能完成解析,而往往需要更多的代码才能正确的完成 XML 的序列化和反序列化处理。而且 XML 对于人类来说不如 JSON 那么直观,XML 标准缺乏对象、数组的标签的明确定义。当结构化的标记可以替代嵌套的标签时,JSON 的优势极为突出。JSON 中的花括号和中括号则明确表示了数据的结构,当然这一优势也包含前文中的问题,在表示元数据时 JSON 不如 XML 准确。

虽然 XML 支持 命名空间 namespace 前缀 prefix ,但这不代表 JSON 没有处理命名冲突的能力。比起 XML 的前缀,它处理命名冲突的方式更简洁,在程序中的处理也更自然。在 JSON 里,每一个对象都在它自己的命名空间中,因此不同对象内的元素名称可以随意重复。在大多数编程语言中,不同的对象中的成员可以包含相同的名字,所以 JSON 根据对象进行名称区分的规则在处理时更加自然。

也许 JSON 比 XML 更优的部分是因为 JSON 是 JavaScript 的子集,所以在 JavaScript 代码中对它的解析或封装都非常的自然。虽然这看起来对 JavaScript 程序非常有用,而其他程序则不能直接从中获益,可实际上这一问题已经被很好的解决了。现在 JSON 的网站的列表上展示了 64 种不同语言的 175 个工具,它们都实现了处理 JSON 所需的功能。虽然我不能评价大多数工具的质量,但它们的存在明确了开发者社区拥抱 JSON 这一现象,而且它们切实简化了在不同平台使用 JSON 的难度。

二者的动机

简单地说,XML 的目标是标记文档。这和 JSON 的目标想去甚远,所以只要用得到 XML 的地方就尽管用。它使用树形的结构和包含语义的文本来表达混合内容以实现这一目标。在 XML 中可以表示数据的结构,但这并不是它的长处。

JSON 的目标是用于数据交换的一种结构化表示。它直接使用对象、数组、数字、字符串、布尔值这些元素来达成这一目标。这完全不同于文档标记语言。正如上面说的那样,JSON 没有原生支持 混合内容 mixed content 的记录。

软件

这些主流的开放 API 仅提供 XML: 亚马逊产品广告 API Amazon Product Advertising API

这些主流 API 仅提供 JSON: 脸书图 API Facebook Graph API 谷歌地图 API Google Maps API 推特 API Twitter API 、AccuWeather API、Pinterest API、Reddit API、Foursquare API。

这些主流 API 同时提供 XML 和 JSON: 谷歌云存储 Google Cloud Storage 领英 API Linkedin API 、Flickr API。

根据 可编程网络 Programmable Web 9 的数据,最流行的 10 个 API 中只有一个是仅提供 XML 且不支持 JSON 的。其他的要么同时支持 XML 和 JSON,要么只支持 JSON。这表明了大多数应用开发者都更倾向于使用支持 JSON 的 API,原因大概是 JSON 更快的处理速度与良好口碑,加之与 XML 相比更加轻量。此外,大多数 API 只是传递数据而非文档,所以 JSON 更加合适。例如 Facebook 的重点在于用户的交流与帖子,谷歌地图则主要处理坐标和地图信息,AccuWeather 就只传递天气数据。总之,虽然不能说天气 API 在使用时究竟是 JSON 用的多还是 XML 用的多,但是趋势明确偏向了 JSON。 10 11

这些主流的桌面软件仍然只是用 XML:Microsoft Word、Apache OpenOffice、LibraOffice。

因为这些软件需要考虑引用、格式、存储等等,所以比起 JSON,XML 优势更大。另外,这三款程序都支持混合内容,而 JSON 在这一点上做得并不如 XML 好。举例说明,当用户使用 Microsoft Word 编辑一篇论文时,用户需要使用不同的文字字形、文字大小、文字颜色、页边距、段落格式等,而 XML 结构化的组织形式与标签属性生来就是为了表达这些信息的。

这些主流的数据库支持 XML:IBM DB2、Microsoft SQL Server、Oracle Database、PostgresSQL、BaseX、eXistDB、MarkLogic、MySQL。

这些是支持 JSON 的主流数据库:MongoDB、CouchDB、eXistDB、Elastisearch、BaseX、MarkLogic、OrientDB、Oracle Database、PostgreSQL、Riak。

在很长一段时间里,SQL 和关系型数据库统治着整个数据库市场。像 甲骨文 Oracle 微软 Microsoft 这样的软件巨头都提供这类数据库,然而近几年 NoSQL 数据库正逐步受到开发者的青睐。也许是正巧碰上了 JSON 的普及,大多数 NoSQL 数据库都支持 JSON,像 MongoDB、CouchDB 和 Riak 这样的数据库甚至使用 JSON 来存储数据。这些数据库有两个重要的特性是它们适用于现代网站:一是它们与关系型数据库相比 更容易扩展 more scalable ;二是它们设计的目标就是 web 运行所需的核心组件。 12 由于 JSON 更加轻量,又是 JavaScript 的子集,所以很适合 NoSQL 数据库,并且让这两个品质更容易实现。此外,许多旧的关系型数据库增加了 JSON 支持,例如 Oracle Database 和 PostgreSQL。由于 XML 与 JSON 间的转换比较麻烦,所以大多数开发者会直接在他们的应用里使用 JSON,因此开发数据库的公司才有支持 JSON 的理由。(LCTT 译注:NoSQL 是对不同于传统的关系数据库的数据库管理系统的统称。参考来源 13

未来

对互联网的种种变革中,最让人期待的便是 物联网 Internet of Things (IoT)。这会给互联网带来大量计算机之外的设备,例如手表、温度计、电视、冰箱等等。这一势头的发展良好,预期在不久的将来迎来爆发式的增长。据估计,到 2020 年时会有 260 亿 到 2000 亿的物联网设备被接入互联网。 14 15 几乎所有的物联网设备都是小型设备,因此性能比笔记本或台式电脑要弱很多,而且大多数都是嵌入式系统。因此,当它们需要与互联网上的系统交换数据时,更轻量、更快速的 JSON 自然比 XML 更受青睐。 16 受益于 JSON 在 web 上的快速普及,与 XML 相比,这些新的物联网设备更有可能从使用 JSON 中受益。这是一个典型的梅特卡夫定律的例子,无论是 XML 还是 JSON,抑或是什么其他全新的格式,现存的设备和新的设备都会从支持最广泛使用的格式中受益。

Node.js 是一款服务器端的 JavaScript 框架,随着她的诞生与快速成长,与 MongoDB 等 NoSQL 数据库一起,让全栈使用 JavaScript 开发成为可能。这些都预示着 JSON 光明的未来,这些软件的出现让 JSON 运用在全栈开发的每一个环节成为可能,这将使应用更加轻量,响应更快。这也是任何应用的追求之一,所以,全栈使用 JavaScript 的趋势在不久的未来都不会消退。 17

此外,另一个应用开发的趋势是从 SOAP 转向 REST。 18 19 20 XML 和 JSON 都可以用于 REST,可 SOAP 只能使用 XML。

从这些趋势中可以推断,JSON 的发展将统一 Web 的信息交换格式,XML 的使用率将继续降低。虽然不应该把 JSON 吹过头了,因为 XML 在 Web 中的使用依旧很广,而且它还是 SOAP 的唯一选择,可考虑到 SOAP 到 REST 的迁移,NoSQL 数据库和全栈 JavaScript 的兴起,JSON 卓越的性能,我相信 JSON 很快就会在 Web 开发中超过 XML。至于其他领域,XML 比 JSON 更好的情况并不多。

角注


  1. Introducing JSON
  2. XML Tutorial
  3. JSON vs. XML: Some hard numbers about verbosity
  4. Comparison of JSON and XML Data Interchange Formats: A Case Study
  5. A comparison of data serialization formats for optimal efficiency on a mobile platform
  6. Comparison of JSON and XML Data Interchange Formats: A Case Study
  7. A comparison of data serialization formats for optimal efficiency on a mobile platform
  8. Introducing JSON
  9. Most Popular APIs: At Least One Will Surprise You
  10. Why JSON will continue to push XML out of the picture
  11. Thousands of APIs Paint a Bright Future for the Web
  12. Why JSON will continue to push XML out of the picture
  13. How JSON sparked NoSQL – and will return to the RDBMS fold
  14. A Simple Explanation Of ‘The Internet Of Things’
  15. Proofpoint Uncovers Internet of Things (IoT) Cyberattack
  16. Why JSON will continue to push XML out of the picture
  17. Why JSON will continue to push XML out of the picture
  18. Thousands of APIs Paint a Bright Future for the Web
  19. 3,000 Web APIs: Trends From A Quickly Growing Directory
  20. How REST replaced SOAP on the Web: What it means to you

via: https://www.cs.tufts.edu/comp/150IDS/final_papers/tstras01.1/FinalReport/FinalReport.html

作者:TOM STRASSNER 选题:lujun9972 译者:wwhio 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出