正如标题所示,我需要将日志数据附加到XML文件而不缓冲到RAM。 XML文件由LogEntry元素组成,其中包含82个包含数据的子元素。这些文件可能变得非常大,因为它将构成Windows CE6程序的一部分,我们的内存非常有限。
经过大量研究后,最常见的方法是使用XDocument
或Linq to XML
来阅读现有文档,然后再附加并写出新文档。在演唱会中使用XmlWriter
和XmlReader
似乎是我追加到文件的最佳方式,但到目前为止我的所有尝试都非常不切实际,并且需要IF语句来指示要写的内容以防止复制或数据少的元素被写入。
我正在做的事情的本质是:
//Create an XmlReader to read current WorkLog.
using (XmlReader xmlRead = XmlTextReader.Create("WorkLog.xml"))
{
//Create a XmlWriterSettings and set indent
//to true to correctly format the document
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.Indent = true;
writerSettings.IndentChars = "\t";
//Create a new XmlWriter to output to
using (XmlWriter xmlWriter = XmlWriter.Create("New.xml", writerSettings))
{
//Starts the document
xmlWriter.WriteStartDocument();
//While the XmlReader is still reading (essentially !EOF)
while (xmlRead.Read())
{
//FSM to direct writing of OLD Log data to new file
switch (xmlRead.NodeType)
{
case XmlNodeType.Element:
//Handle the copying of an element node
//Contains many if statements to handle root node &
//attributes and to skip nodes that contain text
break;
case XmlNodeType.Text:
//Handle the copying of an text node
break;
case XmlNodeType.EndElement:
//Handle the copying of an End Element node
break;
}
}
xmlWriter.WriteEndDocument();
}
}
我确信我可以通过这种方式附加到文件中,但这样做是非常不切实际的 - 有没有人知道我的搜索时间没有出现的任何内存有效方法?
如果需要,我很乐意发布我目前的代码来执行此操作 - 但正如我所提到的那样,它非常大并且实际上非常讨厌,所以我暂时将其删除。
答案 0 :(得分:3)
如果您已了解xml结构,请考虑使用流编写器。 1.打开文件作为文件流 2.将点移动到要替换的标记,例如:,将您的点(位置)移动到“<” 3.以正确的xml格式写入日志数据,并在写入结束时写下“”
“使用文本编辑器处理xml文件”
答案 1 :(得分:2)
如果hack是合理的,我会转到文件的末尾,回过end标签并写入新元素和结束标记。为了进一步改进,您甚至可以缓存最后一个元素开头的偏移量。
答案 2 :(得分:1)
只有使用XmlReader才能在内存中加载完整的XML。它也不支持修改,但您可以通过修改从源文档复制XML。没有其他办法。
将XML解析为文本文档看起来很难。
最好使用类XmlReader / XmlWriter进行解析,并且已经使用您自己的类实现使用Visitor或State GoF模式实现了crud逻辑。访问者模式将减少if-s的数量,并使您的设计易于扩展。即使你不想使用XmlReader / XmlWriter解析XML文档,我建议你在这种情况下使用它们。
答案 3 :(得分:1)
假设日志文件是这样的(只有两个级别):
<logs>
<Log>abc1</Log>
<Log>abc1</Log>
<Log>abc1</Log>
</logs>
我使用FileStream
寻找结束并阅读结束元素。
private static void Append(string xmlElement)
{
const byte lessThan = (byte) '<';
using (FileStream stream = File.Open(@"C:\log.xml", FileMode.OpenOrCreate))
{
if (stream.Length == 0)
{
byte[] rootElement = Encoding.UTF8.GetBytes("<Logs></Logs>");
stream.Write(rootElement, 0, rootElement.Length);
}
List<byte> buffer = new List<byte>();
stream.Seek(0, SeekOrigin.End);
do
{
stream.Seek(-1, SeekOrigin.Current);
buffer.Insert(0, (byte) stream.ReadByte());
stream.Seek(-1, SeekOrigin.Current);
} while (buffer[0] != lessThan);
byte[] toAdd = Encoding.UTF8.GetBytes(xmlElement);
stream.Write(toAdd, 0, toAdd.Length);
stream.Write(buffer.ToArray(), 0, buffer.Count);
}
}
答案 4 :(得分:1)
您使用XmlReader
的方法实际上是要走的路......但正如您所说,这是非常不切实际的。
黑客是否合理?
原因是XML有许多您可能遇到的功能,需要您从上到下阅读它。通常XmlReader
会处理这些情况,为您留下普通标签等等。例如,给出以下声明:
<!ENTITY % pub "Éditions Gallimard" >
<!ENTITY rights "All rights reserved" >
<!ENTITY book "La Peste: Albert Camus, © 1947 %pub;. &rights;" >
然后实体book
的替换文本是:
La Peste: Albert Camus,
© 1947 Éditions Gallimard. &rights;
如果您还没有阅读ENTITY
标签,则无法进行“翻译”。到正确的XML。也就是说,幸运的是,很多人都没有使用这种结构,所以可以假设你的XML没有使用它们来重写根标签。
也就是说,XML中关闭标记的唯一有效方法是在尾随</Foo>
之前使用带有可选空格的>
。 (见http://www.w3.org/TR/2008/REC-xml-20081126/#sec-starttags)。这基本上意味着你可以跳到最后,读取足够的数据,检查它是否包含结束标记 - 如果包含结束标记,你可以插入自己的代码。如果没有,请再回头再试一次。
讨厌的小编码
最后要注意的是文件的编码。虽然您可以从流构造XmlTextReader
,但流使用字节,读者会检测编码并开始读取。幸运的是,XmlTextReader
将Encoding
公开为属性,因此您可以使用它。编码很重要,因为每个字符可能需要不超过1个字节;特别是当你遇到UTF-16或UTF-32时,这可能是一个问题。处理此问题的方法是将令牌转换为字节,然后在字节上进行匹配。
root =预告片假设
因为我真的不想检查空格并跟踪&#39;&gt;&#39; (参见上面的W3C链接),我还假设它是一个有效的XML文件,这意味着每个开始标记也都是关闭的。这意味着您只需检查</root
,即可使匹配过程更加轻松。 (注意:您甚至可能只检查文件中的最后一个</
,但我更喜欢我的代码对错误的XML 更强大一些
全部放在一起
这里......(我还没有对它进行过测试,但它或多或少会起作用)
public bool FindAppendPoint(Stream stream)
{
XmlTextReader xr = new XmlTextReader(stream);
string rootElement = null;
while (xr.Read())
{
if (xr.NodeType == XmlNodeType.Element)
{
rootElement = xr.Name;
break;
}
}
if (rootElement == null)
{
// Well, apparently there's no root... You can start a new file I suppose
return false;
}
else
{
long start = stream.Position; // the position we're currently reading (end of start tag)
long len = stream.Length;
long end = Math.Min(start, len - 1024);
byte[] endTag = xr.Encoding.GetBytes("</" + rootElement);
while (end >= start)
{
byte[] data = new byte[len - end];
stream.Seek(start, SeekOrigin.Begin);
stream.Read(data, 0, data.Length); // FIXME: read returns an int that we should use!!!
// Loop backwards till we find the end tag
for (int i = data.Length - endTag.Length; i >= 0; --i)
{
int j;
for (j = 0; j < endTag.Length && endTag[j] == data[i + j]; ++j) { }
if (j == endTag.Length)
{
// We found a match!
stream.Seek(len - data.Length - i, SeekOrigin.Begin);
AppendXml(stream, xr.Encoding)
return true;
}
}
// Hmm, we've found </xml with a lot of spaces... oh well
//
// It's okay to skip back a bit, just have to make sure that we don't skip <0
if (end == start)
{
end = start - 1; // end the loop
}
else
{
end = Math.Min(start, end - 1024);
}
}
// Nope, no go.
return false;
}
}