いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Linq入門記-89 (LINQ to XML, 巨大XMLファイルの読み込みと変換処理, XStreamingElement)


LINQ to XMLでのXStreamingElementについて.

過去の内容は以下から見れます。よろしければご参照くださいませ。


今回は、XStreamingElementについて.
このクラスは、遅延評価を行うクラスで、主に巨大なXMLファイルを読み込みながら変換処理を行い
別のXMLツリーを構築する際などに利用できます。


このクラスの説明については、Gushwellさんがメルマガで書かれている内容が一番分かりやすいです。


実際に利用する際は、大抵XmlReaderとyieldの仕組みを事前に作っておく必要があります。
XmlReaderで巨大ファイルを逐次読み込みし、それをXStreamingElementで変換処理するイメージです。
これにより、メモリ量が大幅に節約できます。


尚、XStreamingElementはXElementに似ていますが、継承関係はありません。


Gushwellさんの書かれているサンプルと似てますが、以下、サンプルです。

namespace Gsf.Samples
{
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Linq;
  using System.Xml;
  using System.Xml.Linq;

  #region LinqSamples-85
  /// <summary>
  /// LINQ to XMLのサンプルです.
  /// </summary>
  /// <remarks>
  /// XStreamingElementのサンプルです。
  /// </remarks>
  public class LinqSamples85 : IExecutable
  {
    public void Execute()
    {
      //
      // XStreamingElement
      //   XStreamingElementは、遅延評価を行うクラス。
      //   主に、巨大なXMLデータを変換する際に利用できる.
      //
      //   参考URL:
      //     http://msdn.microsoft.com/ja-jp/library/system.xml.linq.xstreamingelement.aspx
      //     http://melma.com/backnumber_120830_4496326/
      //     http://msdn.microsoft.com/ja-jp/library/system.xml.linq.xnode.readfrom.aspx
      //     http://msdn.microsoft.com/ja-jp/library/system.xml.xmlreader.movetocontent.aspx
      //
      //   実際に利用する際は、ほとんどの場合がXmlReaderとyieldの仕組みを事前に作っておかないといけない。
      //   XmlReaderで巨大ファイルを逐次読み込みし、それをXStreamingElementで変換処理する。
      //
      // 以下の処理では、どの程度メモリを消費しているのかを確認するために
      // GC.GetTotalMemoryで消費量を表示している.
      Console.WriteLine("1:{0}", GC.GetTotalMemory(true));

      //
      // 巨大XMLファイルを作成.
      //
      var root = BuildSampleXml(CreateSampleXmlFile());

      Console.WriteLine("2:{0}", GC.GetTotalMemory(true));

      //
      // 普通にXElementを利用して変換処理.
      //
      var result = ConvertXml(root);

      Console.WriteLine("3:{0}", GC.GetTotalMemory(true));

      //
      // XStreamingElementを利用して変換処理.
      //
      var result2 = ConvertXml2(root);

      Console.WriteLine("4:{0}", GC.GetTotalMemory(true));

      //
      // XStreamingElementで変換したデータを出力.
      //
      result2.Save(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "converted2.xml"));

      Console.WriteLine("5:{0}", GC.GetTotalMemory(true));

      //
      // ファイルの読み込みに、XmlReader+yieldを利用してXStreamingElementで変換処理.
      //
      var result3 = ConvertXml3();

      Console.WriteLine("6:{0}", GC.GetTotalMemory(true));

      //
      // XStreamingElementで変換したデータを出力.
      //
      result3.Save(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "converted3.xml"));

      Console.WriteLine("7:{0}", GC.GetTotalMemory(true));
    }

    string CreateSampleXmlFile()
    {
      //
      // 巨大なXMLファイルをデスクトップに作成.
      //
      var dirPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
      var filePath = Path.Combine(dirPath, "toobig.xml");

      if (File.Exists(filePath))
      {
        File.Delete(filePath);
      }

      //
      // <root>
      //   <data>
      //     <code>...</code>
      //     <name>...</name>
      //   </data>
      //   .
      //   .
      //   .
      // </root>
      //
      // の構造を持つXMLファイルを作成.
      //
      var doc = new XDocument
                (
                  new XElement
                  (
                    "root",
                    from i in Enumerable.Range(1, 100000)
                    select new XElement
                           (
                             "data",
                             new XElement("code", string.Format("{0:D5}", i)),
                             new XElement("name", string.Format("name-{0:D5}", i))
                           )
                  )
                );

      doc.Save(filePath);

      return filePath;
    }

    XElement BuildSampleXml(string filePath)
    {
      return XElement.Load(filePath);
    }

    XElement ConvertXml(XElement original)
    {
      var result = new XElement
                   (
                     "newroot",
                     from elem in original.Elements()
                     select new XElement
                     (
                       "newdata",
                       new XAttribute("code", elem.Element("code").Value),
                       new XAttribute("name", elem.Element("name").Value)
                     )
                   );

      return result;
    }

    XStreamingElement ConvertXml2(XElement original)
    {
      var result = new XStreamingElement
                   (
                     "newroot",
                     from elem in original.Elements()
                     select new XElement
                     (
                       "newdata",
                       new XAttribute("code", elem.Element("code").Value),
                       new XAttribute("name", elem.Element("name").Value)
                     )
                   );

      return result;
    }

    XStreamingElement ConvertXml3()
    {
      var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "toobig.xml");

      var result = new XStreamingElement
                   (
                     "newroot",
                     from elem in StreamTooBigXml(filePath)
                     select new XElement
                     (
                       "newdata",
                       new XAttribute("code", elem.Element("code").Value),
                       new XAttribute("name", elem.Element("name").Value)
                     )
                   );

      return result;
    }

    IEnumerable<XElement> StreamTooBigXml(string filePath)
    {
      using (var reader = XmlReader.Create(filePath))
      {
        reader.MoveToContent();

        while (reader.Read())
        {
          if (reader.NodeType != XmlNodeType.Element)
          {
            continue;
          }

          if (reader.Name != "data")
          {
            continue;
          }

          //
          // XElement.ReadFromを利用すると簡単にXElementを取得出来る.
          //
          var elem = XElement.ReadFrom(reader) as XElement;
          if (elem != null)
          {
            yield return elem;
          }
        }
      }
    }
  }
  #endregion
}


実行すると以下のようになります。

  1:26516
  2:15646052
  3:24446212
  4:24446336
  5:24446460
  6:24446672
  7:24473620


以下、参照リソースです。

================================
過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。