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();
参考:
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>
上面代码中,必须是 &,而不能是 &
示例代码
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文件,& 会被转义成 & 是错误的,使用下面的创建方式
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);
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;
参考