解析大型 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)"

在 JAVA 中解析大型 XML 文档

在 SQL Server 中导入和解析大型 XML 文件(当“正常”方法相当慢时)

使用 stax 和 dom 读取大型 XML 文件