C# - 操作大型XML文件

对于小型XML文件,利用XDocument和XMLDocument可以很方便进行读写(推荐XDocument),但问题是XDocument和XMLDocument是In-Memory类型的,随着文件大小的增大,内存消耗会越来越大,同时读写速度会降低。本文总结如下几种方式操作大型XML文件:

读 - Read

利用XmlReader或XmlTextReader流式加载、解析XML文件。

static IEnumerable<XElement> XStreamingElementHelper(string uri, string FindKey)
{
    XmlReaderSettings settings = new XmlReaderSettings();
    settings.IgnoreComments = true;
    settings.IgnoreWhitespace = true;

    using (XmlReader rd = XmlReader.Create(uri, settings))
    {
        rd.MoveToContent();
        while (!rd.EOF)
        {
            if(rd.NodeTyp == XElement && rd.Name == FindKey)
            {
                XElement item = XElement.ReadFrom(rd) as XElement;
                if (item != null)
                {
                    yield return item;
                }
            }
            else
            {
                rd.Read();
            }
        }
    }
}

其中,内部用XElement操作。  

写 - Write

此部分开始前,先提供一个创建空的 xml 文档的代码

string filePath = Directory.GetCurrentDirectory() + "/123456.xml";
if (!File.Exists(filePath))
{
    XmlDocument xdInit = new XmlDocument();
    StringBuilder sb = new StringBuilder();
    sb.Append("<?xml version=\"1.0\" encoding=\"gb2312\" ?><Root>");
    sb.Append("</Root>");
    string strXml = sb.ToString();
    xdInit.LoadXml(strXml);
    xdInit.Save(filePath);
}

该方法生成的 .xml 文档如下

<?xml version="1.0" encoding="gb2312"?>
<Root>
</Root>

或使用如下代码

if (!System.IO.File.Exists(filePath))
{
    System.IO.File.Create(filePath).Close();
    XDocument xdInit = new XDocument(
        new XElement("Root"));
    xdInit.Save(filePath);
    xdInit = null;
}

该方法生成的 .xml 文档如下

<?xml version="1.0" encoding="utf-8"?>
<Root />

注意,以上2种格式等同,xml 文档默认的编码方式是 utf-8

在拼接 xml 字符串时,要特别注意非法字符的转义处理:

System.Security.SecurityElement.Escape(str)

向已存在的大型XML文件中追加新结点,不能采用In-Memory的方式。推荐如下几种写方法:

1. XStreamingElement

问题引出:如何巨型xml文件

使用示例

 1 // Filepath:带写.xml文件路径;FindKey:待查找结点名称
 2 // xeList:要新增的XElement结点列表
 3 string FindKey = "Task";
 4 var items = XStreamingElementHelper(Filepath, FindKey);
 5 items = items.Concat(xeList); 
 6 var xml = new XStreamingElement
 7     ("Root",
 8         from item in items
 9         select item
10     );
11 
12 // 文件替换
13 var newFile = Filepath + "TEMP";
14 xml.Save(newFile);
15 System.IO.File.Delete(Filepath);
16 System.IO.File.Move(newFile, Filepath);
17 
18 GC.Collect();
XStreamingElement实现写文件写

参考

XStreamingElement类 - msdn

How to: Perform Streaming Transform of Large XML Documents (C#) - msdn

关于XStreamingElement vs XElement;  Linq to Xml:XStreamingElement

2. XML文件包含方法

利用包含文件 .txt 和 .xml实现大型XML文件的有效追加操作以及修改操作:

  • .xml:格式正确的标准XML文件
  • .txt:XML片段

格式正确的XML文件

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Root [<!ENTITY events SYSTEM "people.txt">]>
<Root>&events;</Root>

上面代码中,必须是 &,而不能是 &amp;

示例代码

 1 string fileName = "sqh";
 2 string fileDir = Directory.GetCurrentDirectory() + "/";
 3 string xmlFilepath = fileDir + fileName + ".xml";
 4 string txtFilepath = fileDir + fileName + ".txt";
 5 if(!File.Exists(xmlFilepath))
 6 {
 7     // 创建.txt文件
 8     File.Create(txtFilepath).Close();
 9     
10     // 创建.xml文件
11     string type = string.Format("<!ENTITY events SYSTEM \"{0}.txt\">", fileName);
12     XDocument XDoc = new XDocument(
13         new XDocumentType("Root", null, null, type),
14         new XElement("Root", "&events;")
15         );
16     XDoc.Save(xmlFilepath);
17 }
18 else
19 {
20     StreamWriter sw = File.AppendText(txtFilepath);
21     XmlTextWriter xtw = new XmlTextWriter(sw);
22     xtw.Formatting = Formatting.Indented;  // 缩进格式
23     xtw.Indentation = 2;
24     
25     // 新增结点
26     xtw.WriteStartElement("event");
27     xtw.WriteElementString("key", "value");
28     xtw.WriteEndElement();
29     
30     xtw.WriteWhitespace(Environment.NewLine); // 换行,必须带
31     xtw.Close();
32 }
XML = .xml + .txt

上面代码创建的XML文件,& 会被转义成 &amp; 是错误的,使用下面的创建方式

XmlDocument xd = new XmlDocument();
string head = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
string docType = string.Format("<!DOCTYPE Root [<!ENTITY Persons SYSTEM \"{0}.txt\">]>", txtFileName);
string rootBody = "<Root>&Persons;</Root>";
StringBuilder sb = new StringBuilder();
sb.Append(head); sb.Append(docType); sb.Append(body);
string strXml = sb.ToString();
xd.LoadXml(strXml);
xd.Save(filePath);

注意,每写一次.txt文件,必须添加如下代码,否则会出现结点粘结的情况

xtw.WriteWhitespace(Environment.NewLine);

此处可以使用 StreamWriter,亦可使用 FileStream,代码如下

FileStream filestream = new FileStream(FileName, FileMode.Append);
XmlTextWriter xtw = new XmlTextWriter(filestream, Encoding.Default);
xtw.Formatting = Formatting.Indented;
xtw.Indentation = 2;
... ...
xtw.Close();
filestream.Close();

若使用该 .txt + .xml 方法,应采用如下方式读取 .xml 文件

XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;  // 必须的
settings.ValidationType = ValidationType.None; // 可选的
settings.IgnoreComments = true;
//settings.IgnoreWhitespace = true;  // 视while{}中代码情况
XmlReader rd = XmlReader.Create(filepath, settings);

while (rd.Read())
{
    if (rd.NodeType == XmlNodeType.Element && rd.Name == "person")
    { ... }   
}

注意,因为要使用 XmlReaderSettings 设置读取配置,因此只能使用 XmlReader,而不能使用如下代码

XmlTextReader rd = new XmlTextReader(filepath);

参考修改大型 XML 文件的有效方法 - MSDN

3. log4net

引用:using log4net.dll

问题引出:如何log4net to xml?

如何使用:Write a class Deriving from XmlLayoutBase, override the FormatXml method and instruct your appender to use. 

4. 文件拆分

xml/txt 写文件时,文件会越来越大,可以设置时间点或者文件大小限制,将大文件拆分成多个小文件。

// 文件大小
FileInfo fileInfo = new FileInfo(FilePath);
double fileSize = System.Math.Ceiling(fileInfo.Length / 1024.0);

该方法可以利用 log4net 实现,具体可参考:log4net - sqh

参考

关于XML格式化的问题xml日志乱码的问题

如何使用log4net写XML

posted @ 2017-02-27 22:31  万箭穿心,习惯就好。  阅读(1192)  评论(0编辑  收藏  举报