我有一些非常大的 XML 文件,我需要对其进行解析并将相关数据提取到 csv 文件中,本质上是对 XML 文档执行部分展平。XML 文件将具有存储所有记录的“记录标记”。它看起来很像这样,例如:
<persons>
<person id="1">
<firstname>James</firstname>
<lastname>Smith</lastname>
<middlename></middlename>
<dob_year>1980</dob_year>
<dob_month>1</dob_month>
<gender>M</gender>
<salary currency="Euro">10000</salary>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname></lastname>
<middlename>Rose</middlename>
<dob_year>1990</dob_year>
<dob_month>6</dob_month>
<gender>M</gender>
<salary currency="Dollor">10000</salary>
</persons>
这里的记录标签是“人”,转换为 CSV 的结果如下所示:
_id, dob_month, dob_year,firstname,gender,lastname, middlename salary
1,1,1980, James,M,Smith,,{"_VALUE":10000,"_currency":"Euro"}
2,6,1990, Michael M,, Rose,{"_VALUE":10000,"_currency":"Dollor"}
这可能是不正确的——我很快就输入了它——但你明白了。
有一些限制要记住:
- 该文件非常大 - 1GB+ - 所以我无法将它加载到内存中。
- 我应该只读一次。
- (显然)数据不能丢失或不正确。
好的,所以我目前有一个解析器,它将一个简单的 XML 文件转换为一个给定“ROWTAG”的 csv,这是其中包含记录的标签(person
在本例中)。你可以在这里看到它。
但它有一些限制。我知道如何修复/解决其中的大多数问题,但是如果不打破约束,我无法弄清楚其中的两个问题。
- 根据解析器的实现方式,标签的顺序很重要。假设我有一个如下所示的 xml 文件:
<person id="1">
<firstname>James</firstname>
<middlename></middlename>
<lastname>Smith</lastname>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname>Jordan</lastname>
<middlename>Rose</middlename>
</person>
第一个记录标签中的中间名排在第二位,第二个记录中的中间名排在第三位。这将产生一个如下所示的 CSV 文件:
firstname,middlename,lastname
James,,Smith,
Michael,Jordan,Rose
迈克尔的名字被记录为迈克尔乔丹罗斯,当时应该是迈克尔罗斯乔丹。
- 如果添加、删除或更改属性,程序不会反映这一点。这是因为程序只查看第一个记录元素中的标签,而不关心下一个元素中的标签(如示例一所示)。
让我们举这个例子:
<person id="1">
<firstname>James</firstname>
<middlename></middlename>
<lastname>Smith</lastname>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname>Jordan</lastname>
<middlename>Rose</middlename>
<dob>1/10/11</dob>
</person>
生成的 CSV 如下所示:
firstname,middlename,lastname
James,,Smith,
Michael,Jordan,Rose, 1/10/11
这当然是个大问题,必须解决。
我的解决方案
在找到解决方案之前,我将快速总结一下该程序的工作原理。解析器在 XML 文档中移动,每次遇到标记时,都会将其返回给我的程序。正如我所解释的,我的程序有一个“rowTag”,程序正在寻找它。一旦遇到它,我的程序就会开始查看这个 rowTag 中的所有标签和值,并将它们保存在 StringBuilder 中。当遇到结束 rowTag 时,它将转储该信息。在第一次迭代期间,它还将保存它遇到的所有标头,然后在到达结束标记后转储记录的值之前,它将首先转储标头。
现在......正如我所提到的,这会在保存订单以及任何更改、删除或添加的标签方面产生问题。我有一个解决订单的解决方案,它应该解决未更新的标头问题,但我不确定它是否适用于我的用例(我将在一分钟内解释原因)。
我的想法是有一个类似 hashmap 的东西,它收集标签的值以及它们随着时间的推移遇到的顺序。键是标签的值,值是标签第一次出现的顺序。
当我们在程序中移动时收集记录时,我们会将它们放在一个与哈希图一样大的数组中,并放在它需要的正确位置。如果我们遇到新标签,我们将简单地调整数组的大小并将标签添加到哈希图中,而不是它当前遇到的顺序的值(因为这可能会覆盖某些东西),而是前一个元素的值 + 1 (这将是一个有序的哈希图,所以我会知道前一个元素是什么)。
一旦我们完全完成了程序,我们会将收集到的标题转储到文件的第一行。
所以,让我们看第一个例子:
<person id="1">
<firstname>James</firstname>
<middlename></middlename>
<lastname>Smith</lastname>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname>Jordan</lastname>
<middlename>Rose</middlename>
</person>
第一次运行后的 hashmap 如下所示:
{firstname: 0, middlename: 1, lastname: 2}
当我们到达第二条记录时,中间名和姓氏的位置被交换,我们将简单地将姓氏放在该array[2]
位置,并将中间名放在该array[1]
位置。
如果我们遗漏了一些东西,例如这样的:
<person id="1">
<firstname>James</firstname>
<middlename></middlename>
<lastname>Smith</lastname>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname>Rose</lastname>
</person>
在第二次运行期间,该数组在第二个值中将为空(因为中间名不存在),我们只需将其转换为空字符串,并像往常一样在此处附加一个逗号。
有趣的部分是添加某些内容时:
<person id="1">
<firstname>James</firstname>
<middlename></middlename>
<lastname>Smith</lastname>
</person>
<person id="2">
<firstname>Michael</firstname>
<lastname>Rose</lastname>
<dob></dob>
</person>
这将生成如下所示的 CSV:
firstname,middlename,lastname,dob
James,,Smith
Michael,,Rose,1/10/11
,
第一列在之后没有额外的Smith
内容,但似乎即使这不是有效的 CSV 也可以吗?凉爽的。
无论如何-我认为现在真正的问题来了。我实际上并没有在 java 中使用 bufferedreader/bufferedwriter。我们正在使用 Azure 附带的流读取器/写入器,因为当然所有这些文件都在云上,并且在底层它基本上只是休息 api 调用。所以我认为我不能将标题转储到文件的第一行。无论如何,我什至不确定这是否可能。
所以。有哪位大神有想法吗?