解析大型 XML 文件?
Posted
技术标签:
【中文标题】解析大型 XML 文件?【英文标题】:Parsing large XML files? 【发布时间】:2011-05-09 20:23:46 【问题描述】:我有 2 个 xml 文件,其中 1 个大小为 115mb,另一个大小为 34mb。
Wiile 读取文件 A 有一个名为 desc 的字段,它与文件 B 相关,我从文件 B 中检索字段 id,其中 desc.file A 等于 name.file B。
文件A已经太大了,我必须在文件B里面搜索,而且需要很长时间才能完成。
我怎样才能加快这个过程,或者什么是更好的方法?
我正在使用的当前代码:
#!/usr/bin/perl
use strict;
use warnings;
use XML::Simple qw(:strict XMLin);
my $npcs = XMLin('Client/client_npcs.xml', KeyAttr => , ForceArray => [ 'npc_client' ]);
my $strings = XMLin('Client/client_strings.xml', KeyAttr => , ForceArray => [ 'string' ]);
my ($nameid,$rank);
open (my $fh, '>>', 'Output/npc_templates.xml');
print $fh "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<npc_templates xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"npcs.xsd\">\n";
foreach my $npc ( @ $npcs->npc_client )
if (defined $npc->desc)
foreach my $string (@$strings->string)
if (defined $string->name && $string->name =~ /$npc->desc/i)
$nameid = $string->id;
last;
else
$nameid = "";
if (defined $npc->hpgauge_level && $npc->hpgauge_level > 25 && $npc->hpgauge_level < 28)
$rank = 'LEGENDARY';
elsif (defined $npc->hpgauge_level && $npc->hpgauge_level > 21 && $npc->hpgauge_level < 23)
$rank = 'HERO';
elsif (defined $npc->hpgauge_level && $npc->hpgauge_level > 10 && $npc->hpgauge_level < 15)
$rank = 'ELITE';
elsif (defined $npc->hpgauge_level && $npc->hpgauge_level > 0 && $npc->hpgauge_level < 11)
$rank = 'NORMAL';
else
$rank = $gauge;
print $fh qq|\t<npc_template npc_id="$npc->id" name="$npc->name" name_id="$nameid" rank="$rank" tribe="$npc->tribe" race="$npc->race_type" hp_gauge="$npc->hpgauge_level"/>\n|;
print $fh "</<npc_templates>";
close($fh);
文件A.xml示例:
<?xml version="1.0" encoding="utf-16"?>
<npc_clients>
<npc_client>
<id>200000</id>
<name>SkillZone</name>
<desc>STR_NPC_NO_NAME</desc>
<dir>Monster/Worm</dir>
<mesh>Worm</mesh>
<material>mat_mob_reptile</material>
<show_dmg_decal>0</show_dmg_decal>
<ui_type>general</ui_type>
<cursor_type>none</cursor_type>
<hide_path>0</hide_path>
<erect>1</erect>
<bound_radius>
<front>1.200000</front>
<side>3.456000</side>
<upper>3.000000</upper>
</bound_radius>
<scale>10</scale>
<weapon_scale>100</weapon_scale>
<altitude>0.000000</altitude>
<stare_angle>75.000000</stare_angle>
<stare_distance>20.000000</stare_distance>
<move_speed_normal_walk>0.000000</move_speed_normal_walk>
<art_org_move_speed_normal_walk>0.000000</art_org_move_speed_normal_walk>
<move_speed_normal_run>0.000000</move_speed_normal_run>
<move_speed_combat_run>0.000000</move_speed_combat_run>
<art_org_speed_combat_run>0.000000</art_org_speed_combat_run>
<in_time>0.100000</in_time>
<out_time>0.500000</out_time>
<neck_angle>90.000000</neck_angle>
<spine_angle>10.000000</spine_angle>
<ammo_bone>Bip01 Head</ammo_bone>
<ammo_fx>skill_stoneshard.stoneshard.ammo</ammo_fx>
<ammo_speed>50</ammo_speed>
<pushed_range>0.000000</pushed_range>
<hpgauge_level>3</hpgauge_level>
<magical_skill_boost>0</magical_skill_boost>
<attack_delay>2000</attack_delay>
<ai_name>SummonSkillArea</ai_name>
<tribe>General</tribe>
<pet_ai_name>Pet</pet_ai_name>
<sensory_range>15.000000</sensory_range>
</npc_client>
</npc_clients>
文件B.xml示例:
<?xml version="1.0" encoding="utf-16"?>
<strings>
<string>
<id>350000</id>
<name>STR_NPC_NO_NAME</name>
<body> </body>
</string>
</strings>
【问题讨论】:
【参考方案1】:虽然我无法帮助您了解 Perl 代码的具体细节,但在处理大量 XML 数据时有一些通用指南。广义上讲,有 2 种 XML API - 基于 DOM 和基于 Stream。基于 Dom 的 API(如 XML DOM)将在用户级 API 变为“可用”之前将整个 XML 文档解析到内存中,而使用基于流的 API(如 SAX),实现不需要解析整个 XML 文档.基于流的解析器的一个好处是它们通常使用更少的内存,因为它们不需要一次将整个 XML 文档保存在内存中——这在处理大型 XML 文档时显然是一件好事。查看此处的 XML::Simple 文档,似乎有 may be SAX support available - 你试过了吗?
【讨论】:
在哪里可以找到 Some::SAX::Filter ?我已经安装了 XML::SAX 并按照该页面教程使用了 Some::SAX::Filter 但我无法掌握它。 如果您仔细阅读过文档,或者使用过该模块,您会注意到 XML::Simple 将整个文档加载到内存中。您读到的是它可以充当 SAX 过滤器(从 SAX 流获取它的输入并输出一个)。 DOM 和流对 XML 解析还有更多内容:首先 DOM 只是一种树处理类型,还有其他模式,然后还有其他模式:XML::LibXML 具有拉模式,XML::Twig 对树进行转换树的部分。所以总的来说,这不是一个很有帮助的答案。【参考方案2】:我不是 perl 人,所以对此持保留态度,但我发现 2 个问题:
您要遍历文件 B 中的所有值,直到找到文件 A 中每个元素的正确值为止,这一事实是低效的。相反,您应该对文件 B 中的值使用某种地图/字典。
看起来您在开始处理之前就已经解析了内存中的两个文件。文件 A 最好作为流处理,而不是将整个文档加载到内存中。
【讨论】:
你能给我举个第 1 项的例子吗?关于第 2 项,我现在正在处理,谢谢。 @Guapo:看这里:cs.mcgill.ca/~abatko/computers/programming/perl/howto/hash【参考方案3】:-
从文件 A 中获取所有有趣的“desc”字段并将它们放入哈希中。您只需解析一次,但如果仍然需要太长时间,请查看 XML::Twig。
解析文件 B. 一次并提取您需要的内容。使用哈希。
看起来您只需要部分 xml 文件。 XML::Twig 只能解析您感兴趣的元素,并使用“twig_roots”参数丢弃其余元素。 XML::Simple 虽然更容易上手..
【讨论】:
但我需要来自 fileA 的更多内容 :) 来自 fileA 的几乎所有字段,以及来自 fileB 的 ID 字段通过 desc name 字段连接到文件 A,文件 A 为 115mb,而文件 B 在附近34mb 感谢您的回答。【参考方案4】:这是XML::Twig
用法的示例。主要优点是它不会将整个文件保存在内存中,因此处理速度要快得多。下面的代码试图从问题中模拟脚本的操作。
use XML::Twig;
my %strings = ();
XML::Twig->new(
twig_handlers =>
'strings/string' => sub
$strings lc $_->first_child('name')->text
= $_->first_child('id')->text
,
)->parsefile('B.xml');
print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<npc_templates xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"npcs.xsd\">\n";
XML::Twig->new(
twig_handlers =>
'npc_client' => sub
my $nameid = eval $strings lc $_->first_child('desc')->text ;
# calculate rank as needed
my $hpgauge_level = eval $_->first_child('hpgauge_level')->text ;
$rank = $hpgauge_level >= 28 ? 'ERROR'
: $hpgauge_level > 25 ? 'LEGENDARY'
: $hpgauge_level > 21 ? 'HERO'
: $hpgauge_level > 10 ? 'ELITE'
: $hpgauge_level > 0 ? 'NORMAL'
: $hpgauge_level;
my $npc_id = eval $_->first_child('id')->text ;
my $name = eval $_->first_child('name')->text ;
my $tribe = eval $_->first_child('tribe')->text ;
my $scale = eval $_->first_child('scale')->text ;
my $race_type = eval $_->first_child('race_type')->text ;
print
qq|\t<npc_template npc_id="$npc_id" name="$name" name_id="$nameid" rank="$rank" tribe="$tribe" race="$race_type" hp_gauge="$hpgauge_level"/>\n|;
$_->purge;
)->parsefile('A.xml');
print "</<npc_templates>";
【讨论】:
+1 感谢非常有用的学习资料,非常感谢您解决我的麻烦:)以上是关于解析大型 XML 文件?的主要内容,如果未能解决你的问题,请参考以下文章
如何在资源有限的 Haskell 中解析大型 XML 文件?
使用 XMLreader 读取和解析大型 XML 文件。空值问题
解析大型 XML 文件失败--ERROR:Error Domain=DDXMLErrorDomain Code=1 "(null)"