# Langsam beim SAX-Parsen - woran liegts?



## Ghast (28. Sep 2012)

Hallo,

mein Ziel ist, XML-Dateien zu parsen und daraus neue, nach bestimmten Tags gefilterte XML-Dateien zu erzeugen. Die Dateien können recht groß werden, bisher haben wir eine mit 500 MB, die nur einen Bruchteil der Informationen enthält. Daher hab ich von DOM gleich abgesehen und einen SAX-Parser benutzt. Für das Verarbeiten einer 30-MB-Testdatei braucht das Programm auf meinem Rechner etwas mehr als zwei Minuten. 

Eine Bremse ist mir bereits klar: In den meisten startElement()-Aufrufen wird über eine ArrayList iteriert, um zu ermitteln, ob der aktuelle Tag in die Ausgabe soll oder nicht. Die vorher durch Eingaben erzeugte ArrayList enthält die Tags, die gespeichert werden sollen und die for-Schleife bricht erst ab, wenn der aktuelle Tag in der Liste entdeckt wurde oder sie ganz durchgegangen ist. 
--> Gibt es hier eine schnellere/bessere Alternative zur ArrayList? Ehrlich gesagt habe ich mich mit Collections und Iterables nicht groß beschäftigt. Die Liste wird dynamisch erstellt, ist aber beim Parsen im Prinzip fest - wäre es besser, sie einfach zu einem regulären Array umzuwandeln?

Das scheint jedoch nicht das einzige Problem zu sein. Kommentiere ich die Listeniteration aus und führe einfach nur einen einfachen Test auf Gleichheit mit einem Dummy-Tag durch, benötigt das Programm dennoch fast eine Minute, um das 30-MB-Dokument zu parsen und die mit dem Dummy identischen Tags rauszuschreiben. 

Hier der Parser:


```
public class LexParser extends DefaultHandler {

    private Stack <Tag> evaluateTag;
    private int depth;
    private boolean insideTag = false;
    public ArrayList <Tag> selectedTags;
    private StringBuffer xmltext;
    private boolean header;
    
    // ------------------------------------------------------------------------------
    // Parser
    // ------------------------------------------------------------------------------

    public LexParser (File parsefile, ArrayList list) {
        xmltext = new StringBuffer("");
        depth = 0;
        evaluateTag = new Stack ();
        selectedTags = list;
        SAXParserFactory factory = SAXParserFactory.newInstance();
        try {
            // Ignore external DTD
            factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd",false);
            // Build and start parser
            SAXParser saxParser = factory.newSAXParser();
            saxParser.parse(parsefile,this);
        }
        catch (ParserConfigurationException e) {
            System.out.println("Configuration error");
            return;
        }
        catch (SAXException e) {
            System.out.println("SAX error " + e.getMessage());
            return;
        }
        catch (IOException e) {
            System.out.println("IO Error " + e.getMessage());
            return;
        }
    }

    public void startDocument() {
    }

    public void endDocument() {
    }

    public void characters(char[] ch, int start, int length) {
        // collect header-characters
        if (header) {
            String text = new String (ch, start, length).replaceAll("\n","").replaceAll("\t","");
            if (!text.equals("")) {
                xmltext.append(text);
            }
        }
        else if ((!evaluateTag.empty()) && (evaluateTag.peek().getDepth() == depth-1)) {
            String text = new String (ch, start, length).replaceAll("\n","").replaceAll("\t","");
            if (!text.equals("")) {
                xmltext.append(text);
            }
            else
                for (int i = 1; i < depth; i++) xmltext.append("\t");
        }
    }

    public void startElement(String uri,String localName,String qName,Attributes attributes) throws SAXException {
        depth++;

        // header: evaluate completely
        if (qName.equals("teiHeader") || header) {
            header = true;
            xmltext.append("\n");
            for (int i=1; i<depth; i++) xmltext.append("\t");
            // output tags with their attributes
            xmltext.append("<"+qName);
            int attr = attributes.getLength();
            for (int i=0; i<attr; i++) {
                xmltext.append(" "+attributes.getQName(i)+"=\""+attributes.getValue(i)+"\"");
            }
            xmltext.append(">");
        }
        
        // first 4 layers (TEI, text, body, div): always evaluate
        else if (depth<5) {
            for (int i=1; i<depth; i++) xmltext.append("\t");
            // output tags with their attributes
            xmltext.append("<"+qName);
            int attr = attributes.getLength(); 
            for (int i=0; i<attr; i++) {
                xmltext.append(" "+attributes.getQName(i)+"=\""+attributes.getValue(i)+"\"");
            }
            xmltext.append(">\n");
        }

        // inside an evaluated tag: evaluate that tag and its relevant subtags
        else if ((!evaluateTag.empty()) && (evaluateTag.peek().getDepth() == depth-1) && (qName.equals("string")||qName.equals("numeric")||qName.equals("binary")||qName.equals("symbol")) ) {
            insideTag = true;
            xmltext.append("\n");
            for (int i = 1; i < depth; i++) xmltext.append("\t");
            // output tags with their attributes
            xmltext.append("<"+qName);
            int attr = attributes.getLength();
            for (int i=0; i<attr; i++) {
                xmltext.append(" "+attributes.getQName(i)+"=\""+attributes.getValue(i)+"\"");
             }
             xmltext.append(">");
        }
        
        // everything beneath first layer, not inside evaluated tag: evaluate according to user's choice
        else {
            Tag thistag = new Tag(qName,attributes.getValue(0),"",depth);
  
            for (Tag t: selectedTags) {
                if (t.equals(thistag)) {
                    xmltext.append("\n");
                    for (int i = 1; i < depth; i++)
                        xmltext.append("\t");
                    xmltext.append("<"+qName);
                    int attr = attributes.getLength();
                    for (int i=0; i<attr; i++) {
                        xmltext.append(" "+attributes.getQName(i)+"=\""+attributes.getValue(i)+"\"");
                    }
                    xmltext.append(">");
                    evaluateTag.push(thistag);
                    break; // don't look up rest of the list, if already matched 
                }
            }
        }
    }


    public void endElement(String uri,String localName,String qName) throws SAXException {
        
        // close header
        if (header) {
            xmltext.append("\n");
            for (int i=1; i<depth; i++) xmltext.append("\t");
            xmltext.append("</"+qName+">");
            if (qName.equals("teiHeader")) {
                header = false;
                xmltext.append("\n\n");
            }
        }
        
        // close first layers
        else if (depth<5) {
            xmltext.append("\n");
            for (int i=1; i<depth; i++) xmltext.append("\t");
            xmltext.append("</"+qName+">");
        }

        // close selected tags
        else {
            if (!evaluateTag.empty()) {
                Tag thistag = evaluateTag.peek();
                if ((qName.equals(thistag.getTagName())) && (thistag.getDepth()==depth)) {
                    xmltext.append("\n");
                    for (int i = 1; i < depth; i++) xmltext.append("\t");
                    xmltext.append("</"+qName+">\n");
                    evaluateTag.pop();
                }
                if (insideTag) {
                    xmltext.append("</"+qName+">");
                    insideTag = false;
                }   
            }
        }
        
        depth--;
    }

}
```

Verdächtig in Sachen Tempo finde ich noch den Stack, in dem die zu evaluierenden Tags landen ... aber ich weiß nicht genau, wie sich das eleganter lösen ließe. Der wird in characters(), startElement() und in endElement() abgefragt. 

Gibt es irgendwelche auffällige Bremsen in dem Ding? Bisher musste ich zum Glück nie größere Dateien verarbeiten (XML oder sonstige). Aber daher hab ich mich dann auch nicht wirklich mit Performanz im Allgemeinen beschäftigt.


----------



## parabool (28. Sep 2012)

Prüfe das Enthaltensein in der ArrayList mittels contains = einmalige Abfrage pro aktuellen Tag.
HashSet verwenden = noch schneller.


----------



## Ghast (28. Sep 2012)

Wow. Für die contains-Abfrage musste ich die equals- und hashCode-Methoden der Klasse Tag noch einmal formgemäß überschreiben, aber jetzt funktioniert die Abfrage auf dem HashSet wunderbar. Das 30-MB-Dokument wird in knapp 2,5 Sekunden durchlaufen. 

Danke!


----------

