用Java分割大型XML文件

上周,我被要求用Java编写一些东西,该东西能够将单个30GB XML文件拆分为可配置文件大小的较小部分。 该文件的使用者将是一个中间件应用程序,该应用程序存在XML较大的问题。 在后台,它使用某种DOM解析技术,使它在一段时间后耗尽内存。 由于它是基于供应商的中间件,因此我们无法自行纠正。 最好的选择是创建一些预处理工具,该工具会先将大文件分成多个较小的块,然后再由中间件处理。

XML文件带有一个相应的W3C模式,该模式由强制性头部分和紧随其后嵌套有多个0 .. *数据元素的内容元素组成。 对于演示代码,我以简化形式重新创建了架构:
图式 模式(1) 标头的大小可以忽略。 单个数据元素的重复也很小,可以说少于50kB。 由于数据元素重复的次数,XML太大了。 要求是:

  • 分割后的XML的每一部分都应为语法有效的XML,并且每一部分还应针对原始模式进行验证
  • 该工具应根据架构验证XML,并报告所有验证错误。 验证不得阻塞,并且不可在输出中跳过非验证元素或属性
  • 对于标头,决定将其复制到每个新的输出文件中,而不是将其复制到每个新的输出文件中,并使用一些处理信息和一些默认值来重新生成该标头

因此,使用诸如Unix Split之类的二进制拆分工具是不可能的。 在固定数量的字节之后,这将拆分,从而确保XML损坏。 我不太确定,但是诸如Split之类的工具也不了解编码。 因此,在字节“ x”之后进行拆分不仅会导致在XML元素的中间进行拆分(例如),而且甚至会在字符编码序列的中间进行拆分(例如,在使用经过UTF8编码的Unicode时)。 显然,我们需要更智能的东西。

XSLT作为核心技术也是行不通的。 乍一看,可能会很想尝试:使用XSLT2.0,可以从单个输入文件创建多个输出文件。 甚至可以在转换时验证输入文件。 但是,细节始终是魔鬼。 否则,在Java中进行简单的操作(例如将验证错误写入单独的文件或检查当前输出文件的大小)可能需要自定义Java代码。 对于Xalan和Saxon来说,当然可以有这样的扩展,但是Xalan不是XSLT2.0实现,因此只剩下Saxon。 最后但并非最不重要的一点是,XSLT1.0 / 2.0是非流式的,这意味着它们会将整个源文档读入内存,因此这显然将XSLT排除在了可能性之外。

剩下的唯一选择就是Java XML解析。 当然,在这种情况下,理想的选择是StAX。 我不在这里进行SAX与StAX的比较,事实是StAX能够针对架构的身份进行验证(至少某些解析器可以)并且还可以编写XML。 而且,与SAX相比,API的使用要容易得多,因为基于pull的API提供了对迭代文档的更多控制,并且比SAX的推送方式更令人愉快。 好的,我们需要什么:

  • 能够验证XML的StAX实现
    • Oracle的JDK默认附带SJSXP作为StAX实现,但是此验证无效。
  • 最好具有某种对象/ XML映射技术,用于(重新)创建标头,而不是手动摆弄元素并必须查找正确的数据类型/格式
    • 显然是JAXB。

该代码有点大,无法在此处整体显示。 可以访问源文件,XSD和测试XML
了这里 GitHub上。 它具有Maven pom文件,因此您应该能够在选择的IDE中将其导入。 JAXB绑定编译器将自动编译模式,并将生成的源放在类路径上。

public void startSplitting() throws Exception {XMLStreamReader2 xmlStreamReader = ((XMLInputFactory2) XMLInputFactory.newInstance()).createXMLStreamReader(BigXmlTest.class.getResource("/BigXmlTest.xml"));PrintWriter validationResults = enableValidationHandling(xmlStreamReader);int fileNumber = 0;int dataRepetitions = 0;XMLStreamWriter xmlStreamWriter = openOutputFileAndWriteHeader(++fileNumber); // Prepare first file

第一行创建了StAX流读取器,这意味着我们正在使用游标API。 迭代器API使用XMLEventReader类。 类名中还有一个奇怪的“ 2”,它表示Woodstox的StAX 2功能,其中之一可能是对验证的支持。 从
在这里 :

StAX2 is an experimental API that is intended to extend basic StAX specifications 
in a way that allows implementations to experiment with features before they 
end up in the actual StAX specification (if they do). As such, it is intended 
to be freely implementable by all StAX implementations same way as StAX, but 
without going through a formal JCP process. Currently Woodstox is the only 
known implementation.

可以在“ enableValidationHandling”中看到
源文件(如果需要)。 我将重点介绍重要的部分。 首先,加载XML模式:

XMLValidationSchema xmlValidationSchema = xmlValidationSchemaFactory.createSchema(BigXmlTest.class.getResource("/BigXmlTest.xsd"));

用于将可能的验证结果写入输出文件的回调;

public void reportProblem(XMLValidationProblem validationError) throws XMLValidationException {validationResults.write(validationError.getMessage()+ "Location:"+ ToStringBuilder.reflectionToString(validationError.getLocation(),ToStringStyle.SHORT_PREFIX_STYLE) + "\r\n");}

“ openOutputFileAndWriteHeader”将创建一个XMLStreamWriter(它又是游标API的一部分,迭代器API具有XMLEventWriter),我们可以将其输出或原始XML文件的一部分。 它还将使用JAXB创建我们的标头,并将其写入输出。 默认情况下,使用Schema编译器(xjc)生成JAXB对象。

private XMLStreamWriter openOutputFileAndWriteHeader(int fileNumber) throws Exception {XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();xmlOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);XMLStreamWriter writer = xmlOutputFactory.createXMLStreamWriter(new FileOutputStream(new File(System.getProperty("java.io.tmpdir"), "BigXmlTest." + fileNumber + ".xml")));writer.setDefaultNamespace(DOCUMENT_NS);writer.writeStartDocument();writer.writeStartElement(DOCUMENT_NS, BIGXMLTEST_ROOT_ELEMENT);writer.writeDefaultNamespace(DOCUMENT_NS);HeaderType header = objectFactory.createHeaderType();header.setSomeHeaderElement("Something something darkside");marshaller.marshal(new JAXBElement<HeaderType>(new QName(DOCUMENT_NS, HEADER_ELEMENT, ""), HeaderType.class,HeaderType.class, header), writer);writer.writeStartElement(CONTENT_ELEMENT);return writer;}

在第3行,我们启用“修复名称空间”。 规格说明如下:

javax.xml.stream.isRepairingNamespaces:
Function: Creates default prefixes and associates them with Namespace URIs.
Type: Boolean
Default Value: False
Required: Yes

我从中了解到,处理默认名称空间是必需的。 事实是,如果未启用,则不会以任何方式编写默认名称空间。 在第6行,我们设置默认名称空间。 设置它实际上不会将其写入流。 因此,需要writeDefaultNamespace(第9行),但这只能在写入start元素之后才能完成。 因此,您必须在编写任何元素之前定义默认名称空间,但是您需要在编写第一个元素之后编写默认名称空间。 理由是StAX需要知道它是否必须为要写yes或no的根元素生成前缀。

在第8行,我们编写了root元素。 指示此元素所属的名称空间很重要。 如果您未指定前缀,则会为您生成一个前缀,或者,在本例中,将不会生成任何前缀,因为StAX知道我们已经设置了默认名称空间。 如果您要删除第6行的默认名称空间指示,则将为根元素添加前缀(带有随机前缀),例如:<wstxns1:BigXmlTest xmlns:wstxns1 =“ http:// www ...接下来,我们编写默认名称空间,它将被写入先前开始的元素(顺便说一句,为了对此顺序有更深入的了解,请参阅这篇不错的文章 )在第11-14行中,我们使用JAXB生成的模型创建标头,然后让我们的JAXB marshaller直接将其写到我们的StAX输出流。

重要提示: JAXB编组器以片段模式初始化,否则它将开始添加XML声明,这对于独立文档是必需的,当然,在现有文档中间是不允许的:

marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);

附带说明一下:在此示例中,JAXB集成并不是真正有用,它会增加复杂性并占用更多代码行,然后仅使用XMLStreamWriter添加元素即可。 但是,如果您有一个更复杂的结构需要创建并合并到文档中,则具有自动对象映射非常方便。

因此,我们有启用验证的阅读器。 从我们开始遍历源文档的那一刻起,它将同时验证和解析。 然后,我们的writer已经编写了一个初始化的文档和标头,并准备接受更多数据。 最后,我们必须遍历源代码并将每个部分写入输出文件。 如果输出文件变大,我们将换一个新文件:

while (xmlStreamReader.hasNext()) {xmlStreamReader.next();if (xmlStreamReader.getEventType() == XMLEvent.START_ELEMENT&& xmlStreamReader.getLocalName().equals(DATA_ELEMENT)) {if (dataRepetitions != 0 && dataRepetitions % 2 == 0) { // %2 = just for testing: replace this by for example checking the actual size of the current output filexmlStreamWriter.close(); // Also closes any open Element(s) and the documentxmlStreamWriter = openOutputFileAndWriteHeader(++fileNumber); // Continue with next filedataRepetitions = 0;}// Transform the input stream at current position to the output streamtransformer.transform(new StAXSource(xmlStreamReader), new StAXResult(new FragmentXMLStreamWriterWrapper(new AvoidDefaultNsPrefixStreamWriterWrapper(xmlStreamWriter, DOCUMENT_NS))));dataRepetitions++;}
}

重要的一点是,我们不断迭代源文档,并检查是否存在Data元素的开头。 如果是这样,我们将相应的元素及其同级元素流式传输到输出。 在我们的简单示例中,我们没有兄弟姐妹,只有文本值。 但是,如果结构更复杂,则所有基础节点将自动复制到输出中。 每隔两个数据元素,我们将循环输出文件。 关闭编写器,并初始化一个新的编写器(当然,可以通过检查文件大小而不是%2来代替此检查)。 如果作家是关闭的,它将自动处理关闭打开的元素并最终关闭文档本身,而无需您自己这样做。 作为将节点从输入流传输到输出的机制,需要注意以下几点:

  • 由于验证,我们不得不使用游标API,因此必须使用XSLT将节点及其兄弟节点传输到输出。 XSLT具有一些默认模板,如果您未专门指定XSL,则将调用这些模板。 在这种情况下,它将输入转换为给定的输出。
  • 需要一个自定义的FragmentXMLStreamWriterWrapper ,我在JavaDoc中对此进行了记录。 再次将这个包装器包装在PreventDefaultNsPrefixStreamWriterWrapper中 。 最后一个原因是默认的XSLT模板无法识别源文档中的默认名称空间。 一分钟内提供更多信息(或搜索避免使用DefaultDefaultNsPrefixStreamWriterWrapper)。
  • 您使用的转换器必须是Oracle JDK的内部版本。 在初始化转换器的地方,我们直接引用内部TransformerFactory的实例: com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl然后创建正确的转换器: Transformer = new TransformerFactoryImpl()。newTransformer(); 通常,您将使用TransformerFactory.newInstance()并使用classpath上可用的转换器。 但是,解析器和转换器可以通过提供META-INF /服务来安装自己。 如果另一个转换器(例如默认的Xalan,而不是重新打包的JDK版本)将在类路径上,则转换将失败。 原因是显然只有JDK内部版本才可以从StAXSource转换为StAXResult
  • 转换器实际上将让我们的XMLStreamReader在迭代过程中继续。 因此,在处理完一个数据元素之后,理论上阅读器的光标将在下一个数据元素处就绪。 从理论上讲,如果格式化XML,则下一个事件类型可能是空格。 因此,在下一个Data元素实际准备就绪之前,它仍可能需要在while循环中对xmlStreamReader.next()进行一些迭代。

结果是我们有3个输出文件,每个输出文件都符合原始架构,每个文件都有2个数据元素:
档案
文件 要将大约30GB的XML(我在说我的原始工作分配XML具有更复杂的结构,而不是此处使用的演示XSD)拆分为大约500MB的部分,并花费了大约25分钟的时间。 为了测试内存使用率,我特意将Xmx设置为32MB。 从图中可以看出,内存消耗非常低,并且没有GV开销: bigxmltest-vm 生活是美好的,但并非完全如此。 在那儿,我发现有些尴尬的事情需要小心。

在我的实际场景中,输入XML没有与之关联的名称空间,我很确定它永远不会。 这就是我坚持使用此解决方案的原因。 在演示中,这里只有一个名称空间,并且已经开始使设置更加脆弱。 问题不在于StAX:使用StAX处理名称空间非常简单。 您可以决定具有一个与该模式的目标名称空间相对应的默认名称空间(假设您的模式为elementFormDefault = qualified),并可以为该模式中导入的其他名称空间声明一些带前缀的名称空间。 当XSLT开始干扰输出流时,问题就开始出现(您可能已经注意到了)。 显然,它不会检查已经定义了哪些名称空间或发生其他事情。

结果是,它们通过使用其他前缀重新定义现有名称空间或重置默认名称空间和其他不需要的内容,使文档严重混乱。 如果您需要比默认模板更多的名称空间操作,则可能需要XSL。 如果输入文档使用默认名称空间,则XSLT也会触发异常。 它将尝试注册名称为“ xmlns”的前缀。 不允许这样做,因为xmlns保留用于指示默认名称空间,不能用作前缀。 我为此测试申请的解决方案是忽略任何前缀“ xmlns”,并忽略与xmlns前缀组合的目标名称空间的添加(这就是为什么要使用避免DefaultNsPrefixStreamWriterWrapper)。 前缀和名称空间都需要在PreventDefaultNsPrefixStreamWriterWrapper中进行匹配,因为如果您要使用的输入文档中没有默认名称空间,而是带有前缀(例如<bigxml:BigXmlTest xmlns:bigxml =“ http://…。”> <bigxml:Header …。),那么您就不能忽略添加名称空间(该组合将成为带有“ bigxml”前缀的目标名称空间),因为这只会产生数据元素的前缀而没有名称空间绑定,例如:

<?xml version='1.0' encoding='UTF-8'?>
<BigXmlTest xmlns="http://www.error.be/bigxmltest"><Header><SomeHeaderElement>Something something darkside</SomeHeaderElement></Header><Content><bigxml:Data>Data1</bigxml:Data><bigxml:Data>Data2</bigxml:Data></Content>
</BigXmlTest>

请记住,XML的生产者可以自由选择(还是在elementFormDefault =合格的情况下)选择使用默认命名空间还是为每个元素添加前缀。 该代码应该透明地能够处理这两种情况。 为方便起见,请使用PreventDefaultNsPrefixStreamWriterWrapper代码:

public class AvoidDefaultNsPrefixStreamWriterWrapper extends XMLStreamWriterAdapter {
...@Overridepublic void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {if (defaultNs.equals(namespaceURI) && "xmlns".equals(prefix)) {return;}super.writeNamespace(prefix, namespaceURI);}@Overridepublic void setPrefix(String prefix, String uri) throws XMLStreamException {if (prefix.equals("xmlns")) {return;}super.setPrefix(prefix, uri);}

最后,我还写了一个版本(点击
此处完全适用于GitHub),但这次使用的是StAX迭代器API。 您会注意到,不再需要繁琐的XSLT来流传输到输出。 只需将每个感兴趣的事件添加到输出中即可。 通过首先使用游标API验证输入,然后使用Iterator API解析输入,可以解决缺少验证的问题。 这将花费更长的时间,但是在大多数情况下仍然可以接受。 最重要的是:

while (xmlEventReader.hasNext()) {XMLEvent event = xmlEventReader.nextEvent();if (event.isStartElement() && event.asStartElement().getName().getLocalPart().equals(CONTENT_ELEMENT)) {event = xmlEventReader.nextEvent();while (!(event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(CONTENT_ELEMENT))) {if (dataRepetitions != 0 && event.isStartElement()&& event.asStartElement().getName().getLocalPart().equals(DATA_ELEMENT)&& dataRepetitions % 2 == 0) { // %2 = just for testing: replace this by for example checking the actual size of the current// output filexmlEventWriter.close(); // Also closes any open Element(s) and the documentxmlEventWriter = openOutputFileAndWriteHeader(++fileNumber); // Continue with next filedataRepetitions = 0;}// Write the current event to outputxmlEventWriter.add(event);event = xmlEventReader.nextEvent();if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(DATA_ELEMENT)) {dataRepetitions++;}}}}

在第2行,您将看到返回XMLEvent,其中包含有关当前节点的所有信息。 在第4行上,您看到使用此表单检查元素类型更容易(与其与常量进行比较,还可以使用对象模型)。 在第19行,要将元素从输入复制到输出,我们只需将Event添加到XMLEventWriter。

参考:来自Koen Serneels –技术博客博客的JCG合作伙伴 Koen Serneels 分离Java中的大型XML文件 。

翻译自: https://www.javacodegeeks.com/2013/08/splitting-large-xml-files-in-java.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/367530.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

am82.top 1.php,Droppy v2.1.3 – PHP在线网盘系统

更新跨度比较大从1.4.6-2.1.3的更新日志我都贴出来利V2.1.9 (10 July, 2019)- Added option to define maximum upload chunk size in the admin panel- Fixed an issue where downloads were corruptedV2.1.8 (10 July, 2019)- Fixed an issue where upload password field wa…

抛出异常–缓慢而丑陋

这篇文章是关于历史经验以及最近应用的性能优化技术的。 几年前&#xff0c;我在特定的应用程序中发誓&#xff0c;我不得不发现隐藏在真正聪明的工程“技术”之下的无证行为。 它是一个典型的用于发票的单片Java EE应用程序。 最好忘记确切的代码&#xff0c;但是我记得开发人…

信号与线性系统翻转课堂笔记9——傅里叶变换概念

信号与线性系统翻转课堂笔记9——傅里叶变换 The Flipped Classroom9 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、要点 &#xff08;1&#xff0c;重点&#xff09;…

abstract修饰符

1、一个类不能同时被 abstract 和 final 修饰。 2、如果一个类包含抽象方法&#xff0c;那么该类一定要声明为抽象类&#xff0c;否则将出现编译错误。 3、抽象类可以包含抽象方法和非抽象方法。 4、抽象方法是由子类来实现。 5、抽象方法不能被声明成 final 和 static。 6、任…

埃及分数问题(带乐观估计函数的迭代加深搜索算法-IDA*)

#10022. 「一本通 1.3 练习 1」埃及分数 【题目描述】 在古埃及&#xff0c;人们使用单位分数的和&#xff08;形如 $\dfrac{1}{a}​$​​ 的&#xff0c;$a$ 是自然数&#xff09;表示一切有理数。如&#xff1a;$\dfrac{2}{3} \dfrac{1}{2} \dfrac{1}{6}​$​​&#xff0c…

玩转Jquery中的动画效果(animate方法)

jQuery 动画 - animate() 方法jQuery animate() 方法用于创建自定义动画。语法&#xff1a;$(selector).animate({params},speed,callback);必需的 params 参数定义形成动画的 CSS 属性。可选的 speed 参数规定效果的时长。它可以取以下值&#xff1a;"slow"、"…

matlab 等分矩阵,用matlab根据列拆分矩阵.

使用logical indexingBA(A(:,end)10,:);CA(A(:,end)2,:);回报>> BB 1 4 2 5 102 1 5 6 10>> CC 2 4 5 6 22 3 5 4 2编辑&#xff1a;在回复丹的评论这里是一般情况的扩展e unique(A(:,end));B cell(size(e));for k 1:numel(e)B{k} A(A(:,end)e(k),:);end或者更…

点击网页跟踪php代码的工具,使用ltrace工具跟踪PHP库函数调用的方法

本文实例讲述了使用ltrace工具跟踪PHP库函数调用的方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;可能大家已经很熟悉使用strace来跟踪系统调用&#xff0c;今天介绍一个跟踪库函数的利器ltrace比如我有这么一段PHP代码test.php&#xff1a;$y 1380;$arr array…

HDU 2841 Visible Trees(容斥)题解

题意&#xff1a;有一块&#xff08;1,1&#xff09;到&#xff08;m&#xff0c;n&#xff09;的地&#xff0c;从&#xff08;0&#xff0c;0&#xff09;看能看到几块&#xff08;如果两块地到看的地方三点一线&#xff0c;后面的地都看不到&#xff09;。 思路&#xff1a;…

Jquery获取服务器端控件ID的方法

<asp:textbox runat"server" id"txtMessage" textmode"SingleLine">some strings here...</asp:textbox>但是当这段代码输出到客户端时就变成了这样&#xff1a;<input name"ctl00$txtMessage" id"ctl00_txtMessa…

安全地创建和存储密码

几乎每次涉及用户配置文件时&#xff0c;都必须管理用户凭据&#xff0c;从而能够创建和存储用户密码。 通常应该使用散列密码和盐分密码来准备数据库公开和通过使用Rainbow表进行散列反转的密码。 但是&#xff0c;找到以明文形式存储的密码并不少见&#xff08;很不幸&#…

Swift Defer 延迟调用

1、Defer 在一些语言中&#xff0c;有 try/finally 这样的控制语句&#xff0c;比如 Java。这种语句可以让我们在 finally 代码块中执行必须要执行的代码&#xff0c;不管之前怎样的兴风作浪。在 Swift 2.0 中&#xff0c;Apple 提供了 defer 关键字&#xff0c;让我们可以实现…

mysql strtok,strtok()和strtok_r()

下面的说明摘自于最新的Linux内核2.6.29&#xff0c;说明了strtok()这个函数已经不再使用&#xff0c;由速度更快的strsep()代替/** linux/lib/string.c** Copyright (C) 1991, 1992 Linus Torvalds*//** stupid library routines.. The optimized versions should generally b…

from 下拉框多个值提交_Git commit 多行信息提交

git commit可接受多个消息标志(-m)来允许多行提交原文地址&#xff1a;https://www.stefanjudis.com/today-i-learned/git-commit-accepts-several-message-flags-m-to-allow-multiline-commits/原文作者&#xff1a;Stephan Schneider在命令行上使用git时&#xff0c;您可能已…

处理缓慢的资源泄漏

使用Java监视器查找资源泄漏 查找缓慢的资源泄漏是使应用程序服务器长时间保持正常运行的关键。 在这里&#xff0c;我解释了如何使用Java监视器来发现缓慢的资源泄漏&#xff0c;以及如何验证它们是实际的泄漏&#xff0c;而不仅仅是额外的预分配到某些HTTP连接器或数据库池中…

jquery简单实现点击弹出层效果实例

先看效果图&#xff1a;完整例子&#xff1a; <!-- 渐变弹出层 --><div id"race"><a href"#">点击</a></div><div id"racePop" class"raceShow">这里是弹出层效果</div> <script type&q…

Openfire源码阅读(一)

本篇先分析openfire源码的主要流程&#xff0c;模块细节后续再继续分析&#xff1b; 一、简介&#xff1a; Openfire是开源的实时协作服务器&#xff08;RTC&#xff09;&#xff0c;它是基于公开协议XMPP&#xff08;RFC-3920&#xff09;&#xff0c;并在此基础上实现了XMPP-…

常见的linux命令及其翻译

常见的linux指令 1、ls ll 查看文件信息 2、cd 切换工作目录 cd 或 cd ~ 切换到/home/用户目录 cd. 切换到当前目录 cd.. 切换到上级目录 cd- 切换入上次所在的目录 3、clear 或 ctrl l 清屏 4、pwd 显示当前路径 5、mkdir 创建目录 6、rm 删除文件 rm -r 删除文件夹 7、cp 拷…

php 查询方法all,获取多条:all静态方法

查询多条数据&#xff1a;all( )方法all方法与前节课学习的get方法都是静态方法&#xff0c;可用模型类直接访问2. 源码&#xff1a;/*** 查找所有记录* access public* param mixed $data 主键列表或者查询条件(闭包)* param array|string $with 关联预查询* param b…

Google GSON入门

在Java世界中&#xff0c;JSON已成为事实上的XML数据交换格式标准&#xff0c;因为它的易用性和传输效率高。 如果您不了解JSON&#xff0c;那就是Javascript对象表示法&#xff0c;这是一种基于文本的数据交换格式&#xff0c;是名称-值的集合&#xff0c;其中名称严格是字符…