Java SAX 解析

Posted

技术标签:

【中文标题】Java SAX 解析【英文标题】:Java SAX Parsing 【发布时间】:2012-01-25 23:36:53 【问题描述】:

我需要解析一个 XML 流。由于我只需要执行一次并构建我的 java 对象,因此 SAX 看起来是自然的选择。我正在扩展 DefaultHandler 并实现 startElement、endElement 和 characters 方法,在我的类中有成员,我保存当前读取值(在 characters 方法中获取)。

我做我需要的事情没有问题,但我的代码变得相当复杂,我确信没有理由这样做,我可以做不同的事情。 我的 XML 的结构是这样的:

<players>
  <player>
    <id></id>
    <name></name>
    <teams total="2">
      <team>
        <id></id>
        <name></name>
        <start-date>
          <year>2009</year>
          <month>9</month>
        </start-date>
        <is-current>true</is-current>
      </team>
      <team>
        <id></id>
        <name></name>
        <start-date>
          <year>2007</year>
          <month>11</month>
        </start-date>
        <end-date>
          <year>2009</year>
          <month>7</month>
        </end-date>
      </team>
    </teams>
  </player>
</players>

当我意识到文件的多个区域使用了相同的标签名称时,我的问题就开始了。例如,球员和球队都存在 id 和 name。我想创建我的 java 类 Player 和 Team 的实例。在解析时,我保留了布尔标志,告诉我我是否在团队部分,以便在 endElement 中我会知道该名称是团队的名称,而不是玩家的名称等等。

我的代码如下所示:

public class MyParser extends DefaultHandler 

    private String currentValue;
    private boolean inTeamsSection = false;
    private Player player;
    private Team team;
    private List<Team> teams;

    public void characters(char[] ch, int start, int length) throws SAXException 
        currentValue = new String(ch, start, length);
    

    public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException 
        if(name.equals("player"))
            player = new Player();
        
        if (name.equals("teams")) 
            inTeamsSection = true;
            teams = new ArrayList<Team>();
        
        if (name.equals("team"))
            team = new Team();
        
       

    public void endElement(String uri, String localName, String name) throws SAXException 
        if (name.equals("id")) 
            if(inTeamsSection)
                team.setId(currentValue);
            
            else
                player.setId(currentValue);
            
        
        if (name.equals("name"))
            if(inTeamsSection)
                team.setName(currentValue);
            
            else
                player.setName(currentValue);
            
        
        if (name.equals("team"))
            teams.add(team);
        
        if (name.equals("teams"))
            player.setTeams(teams);
            inTeamsSection = false;
        
    

因为在我的真实场景中,除了球队之外,我还有更多的节点给玩家,并且这些节点也有名称和 ID 等标签,我发现自己搞砸了几个类似于 inTeamsSection 的布尔值,并且我的 endElement 方法变得很长并且复杂的条件很多。

我应该做些什么不同的事情?例如,我如何知道名称标签属于什么?

谢谢!

【问题讨论】:

我会说使用 SAX 是大约 7 年前的自然选择。目前自然的选择是使用 JAXB(或 Xtream,或 XmlBeans 或 JibX) 有时您只需要手动进行解析。当您处理兆字节的 XML 时,将其转换为 Java 对象并不是一个好主意。 @ʘleg - 如果您使用 JAXB 从 StAX XMLStreamReader 解组对象,您可以解组较大文档的子部分以管理内存限制。 'subsections' 表示子树,还是 XML 的一部分?如果有像 这样的普通(单级)XML,是否可以将它们一一解组,而不是完全使用 JAXB? 【参考方案1】:

编写 SAX 解析器时有一个巧妙的技巧:允许更改 解析时 XMLReader 的ContentHandler。这允许分离 将不同元素的逻辑解析为多个类,这使得 解析更加模块化和可重用。当一个处理程序看到它的结束元素时 切换回其父级。您实现多少个处理程序将留给 你。代码如下所示:

public class RootHandler extends DefaultHandler 
    private XMLReader reader;
    private List<Team> teams;

    public RootHandler(XMLReader reader) 
        this.reader = reader;
        this.teams = new LinkedList<Team>();
    

    public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException 
        if (name.equals("team")) 
            // Switch handler to parse the team element
            reader.setContentHandler(new TeamHandler(reader, this));
        
    


public class TeamHandler extends DefaultHandler 
    private XMLReader reader;
    private RootHandler parent;
    private Team team;
    private StringBuilder content;

    public TeamHandler(XMLReader reader, RootHandler parent) 
        this.reader = reader;
        this.parent = parent;
        this.content = new StringBuilder();
        this.team = new Team();
    

    // characters can be called multiple times per element so aggregate the content in a StringBuilder
    public void characters(char[] ch, int start, int length) throws SAXException 
        content.append(ch, start, length);
    

    public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException 
        content.setLength(0);
    

    public void endElement(String uri, String localName, String name) throws SAXException 
        if (name.equals("name")) 
            team.setName(content.toString());
         else if (name.equals("team")) 
            parent.addTeam(team);
            // Switch handler back to our parent
            reader.setContentHandler(parent);
        
    

【讨论】:

如果有子团队、玩家等,他们不是都必须包含彼此的引用,这会导致非常紧密耦合吗? 每个处理程序都必须知道它的父处理程序和可能的子处理程序,所以肯定存在一些耦合。但是例如,start-date 的处理程序不需要知道player 的处理程序。 谢谢,我现在正在使用这个 Treak,它对我很有用。正是我在这个用例中所需要的。【参考方案2】:

如果不了解更多关于您的需求,很难提出建议,但是您对“我的代码变得相当复杂”感到惊讶这一事实表明您在选择 SAX 时并没有充分了解情况。 SAX 是一种低级编程接口,具有非常高的性能,但这是因为解析器为您做的工作要少得多,因此您需要自己做更多的工作。

【讨论】:

【参考方案3】:

我做了一些非常相似的事情,但不是让boolean 标志告诉我我处于什么状态,而是测试playerteam 是否不是null。让事情变得更整洁一些。这要求您在将每个元素添加到相关列表后,在检测到每个元素的结尾时将其设置为 null

【讨论】:

【参考方案4】:

我强烈建议您停止解析自己,并使用好的 XML 数据绑定库。 XStream (http://x-stream.github.io/) 可能是个人最喜欢的,但有许多不同的库。它甚至可以在现场解析您的 POJO,而无需任何配置(如果您使用属性名称和复数形式来匹配 XML 结构)。

【讨论】:

【参考方案5】:

如果您需要更漂亮的代码,请使用 StAX,comparison of all XML parsing APIs 表明 StAX 是一个更好的选择。

StAX performance 在大多数测试中也优于任何其他 API 实现。

所以我个人认为没有任何理由继续使用 SAX,除非您正在进行一些与遗留相关的编程。

【讨论】:

以上是关于Java SAX 解析的主要内容,如果未能解决你的问题,请参考以下文章

JAVA之DOM和SAX解析器

java webserver-xml--熟悉SAX解析流程-存储

Java SAX 解析

Java高级特性 第14节 解析XML文档 - SAX 技术

SAX解析

Java web——xml文件读取的解析方式(DOM和SAX)