2

偽のデータに置き換える必要がある個人情報を含む XML ドキュメントがたくさんあります。Person ノードには、次の要素が含まれています。

  • uuid - 必須です。触れないでください。
  • firstName - オプション
  • 姓 - オプション
  • 住所 - オプション
  • 個人ID - 必須

人物が何度も現れる可能性があります。その場合、同じ偽のデータを使用する必要があります。つまり、2 つの Person ノードが同じ personID を持つ場合、両方とも同じ偽の ID を受け取る必要があります。

XML 文字列から DOM ツリーを構築し、それを文字列に書き戻す前にノードを置き換える Java コードをいくつか実装しました。これは問題なく機能しますが、ドキュメントが非常に多いため、より高速なアプローチがあるかどうか疑問に思っていました。たぶん、正規表現や XSLT などを介して?

次にドキュメントの例を示します。

<ADocument>
  <Stuff>
    ...
  </Stuff>
  <OtherStuff>
    ...
  </OtherStuff>
  <Person>
    <uuid>11111111-1111-1111-1111-111111111111</uuid>
    <firstName>Some</firstName>
    <lastName>Person</lastName>
    <personID>111111111111</personID>
  </Person>
  <Person>
    <uuid>22222222-2222-2222-2222-222222222222</uuid>
    <firstName>Another Person</firstName>
    <address>Main St. 2</address>
    <personID>222222222222</personID>
  </Person>
  <Person>
    <uuid>33333333-3333-3333-3333-333333333333</uuid>
    <firstName>Some</firstName>
    <lastName>Person</lastName>
    <personID>111111111111</personID>
  </Person>
  <MoreStuff>
    ...
  </MoreStuff>
</ADocument>

そして、これは私の現在の実装です:

public String replaceWithFalseData(String xmlInstance) {
    Document dom = toDOM(xmlInstance);

    XPathExpression xPathExpression = XPathExpressionFactory.createXPathExpression("//Person");
    List<Node> nodeList = xPathExpression.evaluateAsNodeList(dom);

    for(Node personNode : nodeList) {
        Map<String, Node> childNodes = getChildNodes(personNode);
        String personID = childNodes.get("personID").getTextContent();
        // Retrieve a cached fake person using the ID, or create a new one if none exists.
        Person fakePerson = getFakePerson(personID);

        setIfExists(childNodes.get("firstName"), fakePerson.getFirstName());
        setIfExists(childNodes.get("lastName"), fakePerson.getLastName());
        setIfExists(childNodes.get("address"), fakePerson.getAddress());
        setIfExists(childNodes.get("personID"), fakePerson.getPersonID());
    }

    return toString(dom);
}

public Map<String, Node> getChildNodes(Node parent) {
    Map<String, Node> childNodes = new HashMap<String, Node>();
    for(Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
        if(child.getLocalName() != null) {
            childNodes.put(child.getLocalName(), child);
        }
    }
    return childNodes;
}

public void setIfExists(Node node, String value) {
    if(node != null) {
        node.setTextContent(value);
    }
}
4

4 に答える 4

2

DOM ベースの API を使用しています。Streaming API for XML (StAX)を使用すると、多くの場合、DOM ベースの API よりも優れたパフォーマンスを 実現できます: StAX と DOM の比較

DOM API は StAX よりも多くのメモリを占有するため、パフォーマンスが低下する可能性がありますが、StAX API よりも使いやすくなっています。

あなたの例の実用的なソリューション - 150 MBのxmlファイルでテストされ、10秒で置き換えられました:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;


public class ReplaceXmlWithFakeUser
{
  public static void main(String[] args) throws XMLStreamException, IOException
  {
    XMLInputFactory inFactory = XMLInputFactory.newInstance();
    XMLEventReader eventReader = inFactory.createXMLEventReader(new BufferedInputStream(new FileInputStream("c:\\temp\\persons.xml")));
    XMLOutputFactory factory = XMLOutputFactory.newInstance();
    XMLEventWriter writer = factory.createXMLEventWriter(new BufferedOutputStream(new FileOutputStream("c:\\temp\\fakePersons.xml")));
    XMLEventFactory eventFactory = XMLEventFactory.newInstance();
    while (eventReader.hasNext())
    {
      XMLEvent event = eventReader.nextEvent();

      if (event.getEventType() == XMLEvent.START_ELEMENT &&
        event.asStartElement().getName().toString().equals("Person"))
      {
        //write Person startElement:
        writer.add(event);


        /*
        STEP 1:
        personId is at the end of Person element. Cannot overwrite firstName and address element with fake data yet. Must call getFakePerson() first.
        Iterate till you read Person END element and just remember all events within person element which we will overwrite with fake data in step 2.
         */
        Person fakePerson=null;

        List<XMLEvent> eventsWithinPersonElement = new ArrayList<XMLEvent>();

        event = eventReader.nextEvent();
        while(!(event.getEventType() == XMLEvent.END_ELEMENT && event.asEndElement().getName().toString().equals("Person")))
        {

          eventsWithinPersonElement.add(event);

          if(event.getEventType() == XMLEvent.START_ELEMENT &&
              event.asStartElement().getName().toString().equals("personID"))
          {
            XMLEvent personIDContentEvent = eventReader.nextEvent();

            String personId = personIDContentEvent.asCharacters().toString();
            fakePerson = getFakePerson(personId);

            eventsWithinPersonElement.add(personIDContentEvent);
          }

          event = eventReader.nextEvent();
        }
        XMLEvent personEndElement=event;


        //STEP 2:
        for (Iterator<XMLEvent> eventWithinPersonElementIterator = eventsWithinPersonElement.iterator(); eventWithinPersonElementIterator.hasNext(); )
        {
          XMLEvent eventWithinPersonElement = eventWithinPersonElementIterator.next();

          writer.add(eventWithinPersonElement);

          if(eventWithinPersonElement.getEventType() == XMLEvent.START_ELEMENT &&
              eventWithinPersonElement.asStartElement().getName().toString().equals("personID"))
          {
            writer.add(eventFactory.createCharacters(fakePerson.personId));

            //skip personId event
            eventWithinPersonElementIterator.next();
          }
          if(eventWithinPersonElement.getEventType() == XMLEvent.START_ELEMENT &&
              eventWithinPersonElement.asStartElement().getName().toString().equals("firstName"))
          {
            writer.add(eventFactory.createCharacters(fakePerson.firstName));

            //skip real firstName
            eventWithinPersonElementIterator.next();
          }
          if(eventWithinPersonElement.getEventType() == XMLEvent.START_ELEMENT &&
              eventWithinPersonElement.asStartElement().getName().toString().equals("lastName"))
          {
            writer.add(eventFactory.createCharacters(fakePerson.lastName));

            //skip real firstName
            eventWithinPersonElementIterator.next();
          }
          else if(eventWithinPersonElement.getEventType() == XMLEvent.START_ELEMENT &&
              eventWithinPersonElement.asStartElement().getName().toString().equals("address"))
          {
            writer.add(eventFactory.createCharacters(fakePerson.address));

            //skip real address
            eventWithinPersonElementIterator.next();

          }
        }

        writer.add(personEndElement);
      }
      else
      {
        writer.add(event);
      }
    }
    writer.close();
  }

  private static Person getFakePerson(String personId)
  {
    //create simple fake user...

    Person fakePerson = new Person();
    fakePerson.personId = personId;
    fakePerson.firstName = "fake first name: " + Math.random();
    fakePerson.lastName = "fake last name: " + Math.random();
    fakePerson.address = "fake address: " + Math.random();

    return fakePerson;
  }

  static class Person
  {
    String personId;
    String firstName;
    String lastName;
    String address;

  }
}

入力として使用persons.xml:

<ADocument>
    <Stuff>
        <StuffA></StuffA>
    </Stuff>
    <OtherStuff>
        <OtherStuff>
            <ABC>yada yada</ABC>
        </OtherStuff>
    </OtherStuff>

    <Person>
        <uuid>11111111-1111-1111-1111-111111111111</uuid>
        <firstName>Some</firstName>
        <lastName>Person</lastName>
        <personID>111111111111</personID>
    </Person>
    <Person>
        <uuid>22222222-2222-2222-2222-222222222222</uuid>
        <firstName>Another Person</firstName>
        <address>Main St. 2</address>
        <personID>222222222222</personID>
    </Person>
    <Person>
        <uuid>33333333-3333-3333-3333-333333333333</uuid>
        <firstName>Some</firstName>
        <lastName>Person</lastName>
        <personID>111111111111</personID>
    </Person>

    <MoreStuff>
        <foo></foo>
        <foo>fooo</foo>
        <foo><bar></bar></foo>
        <foo>
            <bar></bar>
            <bar/>
            <bar>bb</bar>
        </foo>
        <bar/>
    </MoreStuff>

</ADocument>

このfakePersons.xml結果の生成:

<?xml version="1.0" encoding="UTF-8"?><ADocument>
    <Stuff>
        <StuffA></StuffA>
    </Stuff>
    <OtherStuff>
        <OtherStuff>
            <ABC>yada yada</ABC>
        </OtherStuff>
    </OtherStuff>

    <Person>
        <uuid>11111111-1111-1111-1111-111111111111</uuid>
        <firstName>fake first name: 0.9518514637129984</firstName>
        <lastName>fake last name: 0.3495378044884426</lastName>
        <personID>111111111111</personID>
    </Person>
    <Person>
        <uuid>22222222-2222-2222-2222-222222222222</uuid>
        <firstName>fake first name: 0.8945739434355868</firstName>
        <address>fake address: 0.40784763231471777</address>
        <personID>222222222222</personID>
    </Person>
    <Person>
        <uuid>33333333-3333-3333-3333-333333333333</uuid>
        <firstName>fake first name: 0.7863207851479257</firstName>
        <lastName>fake last name: 0.09918620445731652</lastName>
        <personID>111111111111</personID>
    </Person>

    <MoreStuff>
        <foo></foo>
        <foo>fooo</foo>
        <foo><bar></bar></foo>
        <foo>
            <bar></bar>
            <bar></bar>
            <bar>bb</bar>
        </foo>
        <bar></bar>
    </MoreStuff>

</ADocument>
于 2013-09-04T13:52:50.793 に答える
0

貢献してくれたみんなに感謝します!DOM 実装、Sergej の StAX 実装、Ben の XSLT 実装、および正規表現を使用した独自の別の実装を使用して、2000 個の XML ドキュメントのセットでパフォーマンス テストを実行しました。結果は次のようになりました。

  • DOM: 23,93 秒
  • StAX: 20,37 秒
  • XSLT: 83,52 秒
  • 正規表現: 7,83 秒

そして、これが勝者です:

public String replaceWithFalseData(String xmlInstance) {
    Pattern personPattern = Pattern.compile("<Person>.*?</Person>", Pattern.DOTALL);
    Matcher personMatcher = personPattern.matcher(xmlInstance);
    StringBuffer xmlBuffer = new StringBuffer();

    while(personMatcher.find()) {
        String personXml = personMatcher.group();

        Pattern idPattern = Pattern.compile("<personID>(.*)</personID>");
        Matcher idMatcher = idPattern.matcher(personXml);
        idMatcher.find();
        String id = idMatcher.group(1);
        Person fakePerson = getFakePerson(id);

        personXml = personXml.replaceFirst("<firstName>.*</firstName>",
                "<firstName>" + fakePerson.getFirstName() + "</firstName>");

        personXml = personXml.replaceFirst("<lastName>.*</lastName>",
                "<lastName>" + fakePerson.getLastName() + "</lastName>");

        personXml = personXml.replaceFirst("<address>.*</address>",
                "<address>" + fakePerson.getAddress() + "</address>");

        personXml = personXml.replaceFirst("<personID>.*</personID>",
                "<personID>" + fakePerson.getPersonID() + "</personID>");

        personMatcher.appendReplacement(xmlBuffer, personXml);
    }

    personMatcher.appendTail(xmlBuffer);
    return xmlBuffer.toString();
}
于 2013-09-06T13:24:53.170 に答える
0

ここで XSLT が役立つかどうかはわかりません。おそらく、私の XSLT に関する知識は十分ではありませんが、XSLT を使用して、既存の XML のデータに基づいて新しい XML 構造を作成します。ここでは反対のことをしたいようです: 同じ構造を維持しながら、動的な値に基づいてデータを更新します。このような XSLT を作成するのは難しいかもしれません。最適化は、XML ごとの Person 要素の数、XML 内の等しい PersonId の数、処理する XML の数など、いくつかのパラメータに依存する可能性があります。大きなファイルを扱っている場合は、メモリ消費を最適化する SAX 実装。同じ XML 内の多数の等しい PersonID と再調整する場合は、DOM のヒット数を減らすために置き換えるために使用する偽のデータの背後にキャッシュ構造を構築できます (ノードをキャッシュされたノードを削除し、uuid を元のもので上書きします)。似たような PersonID を含む小さなファイルが多数ある場合、複数の XML ファイルで同じ偽のデータを使用できることが許容される場合は、クロス XML キャッシュを使用することをお勧めします。

また、必須フィールドとして記載されているので、PersonID に「setIfExists」をドロップできると思います。

于 2013-09-04T13:53:10.293 に答える