JSON 与 XML:关于冗余的一些硬数据






4.71/5 (12投票s)
我写了一个小的 Java 基准测试,将在本文中介绍它的结果。
引言
越来越多的 **JSON** 成为了 **Web** 的 **数据交换格式**,甚至开始渗透到 Web 之外的世界, wherever it can, **取代 XML**,这其中有很多非常充分的理由。但人们选择 **JSON** 往往还有其他原因,这些原因不一定不好,而是基于一些经验性的事实,例如所谓的 **XML 的冗余**。确实,这是你最常听到的论点,例如,看看 **这篇精彩的格式对比**:第一个缺点当然就是“**冗余**”。
这是一个事实论点:如果你的值很小,比如用来表示客户这样的业务对象,那么 XML 的大小增益可能很重要,因为标记的开销(所有闭合标签)相对于承载的信息(例如,你客户的姓名和邮政编码)会变得很重要。
但是,你很少会以 **原始文本格式** 的 **XML** 或 **JSON** 发送大块数据,因为如今服务器和客户端(例如 **Web 浏览器**)都支持实时 **gzip** 压缩工作负载,并且会透明地使用它。所以 JSON 相对于 XML 的大小优势应该会减小,因为 **GZIP** 知道如何因子化 **冗余信息**,比如标记。
至少这看起来是一个合理的猜测,但直觉固然好,但确凿的证据还是需要硬数据来让你信服并对影响有一个量化的概念。所以我写了一个小的 **Java** **基准测试**,将在本文中介绍它的结果。
模型
该**基准测试**模拟了一个非常常见的 Web 开发场景:**序列化一大堆业务数据**,一组两百万个用户。
以下是**用户实体**的不同表示形式。
Java
public class User
{
private int id;
private String name;
public int getId()
{
return id;
}
public String getName()
{
return name;
}
public User(int id, String name)
{
this.id = id;
this.name = name;
}
}
XML
<user><id>%d</id><name>%s</name></user>
请注意,我使用了冗余的格式来清晰地说明这一点;当然,“id
”和“name
”应该实现为**属性**,但有时你别无选择,例如,当你必须符合一个设计糟糕的 XML 模式时。
还有 JSON
{id:%d,name:"%s"}
我们已经可以看到,与 JSON 相比,XML 模板相当冗长。
数据生成
所有数据,即用户 ID 和姓名,都是使用一些辅助方法**随机生成的**,以避免选择固定值时可能出现的任何偏差。
private static final Random random = new Random();
private static final char[] letters = new char[26];
static
{
for (int i = 0; i < 26; ++i)
{
letters[i] = (char) ('a' + i);
}
}
private static int getId()
{
return random.nextInt(99999);
}
private static String getName(int length)
{
char[] chars = new char[length];
for (int i = 0; i < length; ++i)
{
chars[i] = letters[random.nextInt(letters.length)];
}
return new String(chars);
}
private static User[] getUsers(int count)
{
User[] users = new User[count];
for (int i = 0; i < count; ++i)
{
users[i] = new User(getId(), getName(6));
}
return users;
}
由于基准测试试图比较每种文档格式的**格式化开销**的成本,因此值的长度受到限制,以免它们过于普遍:ID 和姓名的长度被限制为 6 个字符。
数据压缩
**压缩过程**基于标准的 **Java GZip** 实现,非常简单。
private static byte[] zip(String string) throws Exception
{
ByteArrayOutputStream memory = new ByteArrayOutputStream();
GZIPOutputStream zip = new GZIPOutputStream(memory);
zip.write(string.getBytes());
zip.close();
return memory.toByteArray();
}
输入是**XML**和**JSON**文档的文本版本,输出是压缩内容的原始二进制表示。
基准测试
这是**基准测试**
- 生成一组随机用户
- 生成这组用户的 XML 和 JSON 表示
- 比较文本文档的大小
- 生成 XML 和 JSON 文档的压缩版本
- 比较压缩文档的大小以及压缩所需的时间
请注意,基准测试**考虑了压缩文档所需的时间**,因为你可能已经猜到,压缩持续时间取决于内容的大小,而**CPU 时间**是一个不能忽视的重要因素。
实现如下
public static void main(String[] args) throws Exception
{
User[] users = getUsers(2000000);
String xml = getXML(users);
String json = getJSON(users);
System.out.println(String.format("xml(%d)/json(%d): %f",
xml.length(), json.length(), 1.0 * xml.length()/json.length()));
long t1 = System.currentTimeMillis();
byte[] xmlZip = zip(xml);
long t2 = System.currentTimeMillis();
byte[] jsonZip = zip(json);
long t3 = System.currentTimeMillis();
System.out.println(String.format("xmlDuration(%d)/jsonDuration(%d): %f",
t2 - t1, t3 - t2, 1.0 * (t2 - t1) / (t3 - t2)));
System.out.println(String.format("xmlZip(%d)/jsonZip(%d): %f",
xmlZip.length, jsonZip.length, 1.0 * xmlZip.length/jsonZip.length));
}
不复杂,但应该能完成任务。
赢家是……
言归正传,结果如下
文本 | Gzip | 压缩时长 | |
XML | 91.78M | 18.74M | 3.38s |
---|---|---|---|
JSON | 49.78M | 17.09M | 2.78s |
XML 开销 | 84.38% | 9.62% | 21.3% |
正如文本和压缩版本所预期的那样,**XML 存在大小开销**,但在文本版本中,这种开销非常重要:**84%**,几乎是两倍大;而在压缩后,这种开销变得不那么显著,**不到 10%**。
为了获得这种大小的收益,我们**消耗了一些额外的 CPU 时间**:压缩 XML 文档比压缩 JSON 文档需要**多 20% 的时间**。
所以,根据你的用例,这可能是完全可以接受的,也可能完全不可接受:如果你的服务器不处理大量请求且从不出现过载,那么额外的 20% 时间不是问题,因为它允许大小大幅减小;但如果你的服务器已经过载,20% 更多的 CPU 负载可能会导致延迟超出警戒线。
结论
正如你所见,虽然 XML 的“**尖括号税**”是真实存在的,但它可以显著降低到一个可接受的水平,但代价是额外的处理时间。请记住,与任何基准测试一样,它只代表其本身的价值,如果数据格式在你的情况下至关重要,你应该进行自己的研究,参考本文,但使用你自己的数据和技术,因为你的结果可能不同。
在我看来,JSON 成为许多应用程序的自然选择,并非因为其**固有的优点**(尽管如上文所示,这些优点是真实的),而是因为它**深度集成到 Web 生态系统**中,因为 JSON 是表示**JavaScript 对象树**的本地方式。
随着**服务器端 JavaScript** 的兴起,特别是 **Node.js** 的出现,JavaScript 不再局限于客户端,它成为确保客户端和服务器之间通信的逻辑选择:JavaScript 使用……JavaScript 与 JavaScript 进行通信。
此外,JSON 被一些**NoSQL 数据库**选择为数据格式,例如 **MongoDB** 或 **CouchDB**,这是使用它来构建**统一栈**的又一个好理由。
要关注博客,请订阅 RSS feed