大量日誌直接 XmlDocument.Load 會耗盡記憶體。本文採用 DOCTYPE 外部實體引用 + XmlWriter 追加寫入 方案:主 XML 僅保留結構與實體引用,實際內容寫入獨立片段檔案,實現 O(1) 記憶體寫入。
XmlLogFile-20110220000120.xml,包含 DOCTYPE 實體宣告與根結構。XmlLogContentFile-20110220000120.txt,純 XML 片段,持續追加。
private static void InitXmlFile(string xmlLogFilePath, string contentFileName, string entityRef) {
var docType = $@"
<!DOCTYPE XmlLogFile [
<!ENTITY {entityRef} SYSTEM ""{contentFileName}""
]>";
var settings = new XmlWriterSettings { Indent = true };
using (var writer = XmlWriter.Create(xmlLogFilePath, settings)) {
writer.WriteStartDocument();
writer.WriteRaw(docType); // 寫入 DOCTYPE 宣告
writer.WriteStartElement("XmlLogFile");
writer.WriteStartElement("XmlLogContent");
writer.WriteEntityRef(entityRef); // 寫入 &Locations;
writer.WriteEndElement(); // XmlLogContent
writer.WriteEndElement(); // XmlLogFile
}
}
// 生成內容:
// <?xml version="1.0" encoding="utf-8"?>
// <!DOCTYPE XmlLogFile [ <!ENTITY Locations SYSTEM "XmlLogContentFile-...txt"> ]>
// <XmlLogFile><XmlLogContent>&Locations;</XmlLogContent></XmlLogFile>
public static void Log(string contentFilePath, object logEntity) {
var settings = new XmlWriterSettings {
Indent = true,
ConformanceLevel = ConformanceLevel.Fragment // 關鍵:片段模式,無 XML 宣告
};
using (var writer = XmlWriter.Create(contentFilePath, settings)) {
// 選項 A:序列化物件
new XmlSerializer(logEntity.GetType()).Serialize(writer, logEntity);
// 選項 B:手寫元素
// writer.WriteStartElement("LogEntry");
// writer.WriteElementString("Time", DateTime.Now.ToString("o"));
// writer.WriteElementString("Message", msg);
// writer.WriteEndElement();
}
}
// 解析時自動展開實體引用
var doc = new XmlDocument { XmlResolver = new XmlUrlResolver() };
doc.Load(xmlLogFilePath);
var content = doc.SelectSingleNode("/XmlLogFile/XmlLogContent").InnerXml; // 已包含片段內容
// 或使用 XDocument (需設定 LoadOptions.SetLineInfo)
var xdoc = XDocument.Load(xmlLogFilePath, LoadOptions.SetLineInfo);
| 方案 | 記憶體 | 寫入效能 | 讀取效能 | 適用場景 |
|---|---|---|---|---|
| XmlDocument 全量載入 | O(N) 全量佔用 | 慢(每次重寫) | 快(記憶體樹) | 小檔案、頻繁查詢 |
| XmlWriter 覆寫整檔 | O(N) 需讀取舊內容 | 慢 | 快 | 中等檔案、低頻寫入 |
| 外部實體 + 片段追加 | O(1) 僅片段緩衝 | 極快(僅追加) | 中(需展開實體) | 大型日誌、高頻寫入、長期保存 |
| 資料庫/NoSQL | 由引擎管理 | 快 | 快(索引) | 結構化查詢、多維分析 |
XmlResolver 可能導致 XXE 攻擊,解析時務必禁用外部實體 或使用安全解析器。lock/FileShare)或採用單寫入器隊列。