Самый быстрый способ добавить новый узел в конец XML? - PullRequest
7 голосов
/ 11 мая 2009

У меня большой XML-файл (около 10 МБ) в следующей простой структуре:

<Errors>
   <Error>.......</Error>
   <Error>.......</Error>
   <Error>.......</Error>
   <Error>.......</Error>
   <Error>.......</Error>
</Errors>

Мне нужно написать и добавить новый узел в конце перед тегом . Какой самый быстрый способ добиться этого в .net?

Ответы [ 10 ]

10 голосов
/ 11 мая 2009

Вам нужно использовать технику включения XML.

Ваш error.xml (не изменяется, просто заглушка. Используется для чтения XML-анализаторами):

<?xml version="1.0"?>
<!DOCTYPE logfile [
<!ENTITY logrows    
 SYSTEM "errorrows.txt">
]>
<Errors>
&logrows;
</Errors>

Ваш файл errorrows.txt (изменяется, синтаксический анализатор xml его не понимает):

<Error>....</Error>
<Error>....</Error>
<Error>....</Error>

Затем, чтобы добавить запись в errorrows.txt:

using (StreamWriter sw = File.AppendText("logerrors.txt"))
{
    XmlTextWriter xtw = new XmlTextWriter(sw);

    xtw.WriteStartElement("Error");
    // ... write error messge here
    xtw.Close();
}

Или вы можете даже использовать .NET 3.5 XElement и добавить текст к StreamWriter:

using (StreamWriter sw = File.AppendText("logerrors.txt"))
{
    XElement element = new XElement("Error");
    // ... write error messge here
    sw.WriteLine(element.ToString());
}

См. Также Статья Microsoft «Эффективные методы изменения больших файлов XML»

7 голосов
/ 11 мая 2009

Во-первых, я бы дисквалифицировал System.Xml.XmlDocument, потому что это DOM , который требует синтаксического анализа и построения всего дерева в памяти, прежде чем оно может быть добавлено. Это означает, что ваши 10 МБ текста будут более 10 МБ в памяти. Это означает, что он «интенсивно использует память» и «занимает много времени».

Во-вторых, я бы дисквалифицировал System.Xml.XmlReader, потому что требует синтаксического анализа всего файла , прежде чем вы сможете дойти до того момента, когда вы сможете добавить его. Вам придется скопировать XmlReader в XmlWriter, поскольку вы не можете его изменить. Это требует дублирования вашего XML в памяти, прежде чем вы сможете добавить к нему.

Более быстрым решением для XmlDocument и XmlReader было бы манипулирование строками (которое имеет свои проблемы с памятью):

string xml = @"<Errors><error />...<error /></Errors>";
int idx = xml.LastIndexOf("</Errors>");

xml = xml.Substring(0, idx) + "<error>new error</error></Errors>";

Отрежьте конечный тег, добавьте новую ошибку и добавьте конечный тег обратно.

Полагаю, вы могли бы сойти с ума от этого, урезать файл до 9 символов и добавить к нему. Не нужно будет читать файл и позволит ОС оптимизировать загрузку страницы (нужно будет загрузить только последний блок или что-то в этом роде).

System.IO.FileStream fs = System.IO.File.Open("log.xml", System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite);
fs.Seek(-("</Errors>".Length), System.IO.SeekOrigin.End);
fs.Write("<error>new error</error></Errors>");
fs.Close();

Это вызовет проблему, если ваш файл пуст или содержит только «», оба из которых можно легко обработать, проверив длину.

3 голосов
/ 11 мая 2009

Самый быстрый способ, вероятно, - прямой доступ к файлу.

using (StreamWriter file = File.AppendText("my.log"))
{
    file.BaseStream.Seek(-"</Errors>".Length, SeekOrigin.End);
    file.Write("   <Error>New error message.</Error></Errors>");
}

Но вы потеряете все приятные функции XML и легко повредите файл.

1 голос
/ 11 мая 2009

Попробуйте это:

        var doc = new XmlDocument();
        doc.LoadXml("<Errors><error>This is my first error</error></Errors>");

        XmlNode root = doc.DocumentElement;

        //Create a new node.
        XmlElement elem = doc.CreateElement("error");
        elem.InnerText = "This is my error";

        //Add the node to the document.
        if (root != null) root.AppendChild(elem);

        doc.Save(Console.Out);
        Console.ReadLine();
1 голос
/ 11 мая 2009

Я бы использовал XmlDocument или XDocument для загрузки вашего файла и затем соответствующим образом управлял им.

Затем я бы посмотрел на возможность кэширования этого XmlDocument в памяти, чтобы вы могли быстро получить доступ к файлу.

Для чего вам нужна скорость? У вас уже есть узкое место в производительности или вы ожидаете его?

0 голосов
/ 11 марта 2019

Я пытался использовать код, который предлагали другие ответы, но столкнулся с проблемой, когда иногда вызов .length в моих строках не совпадал с количеством байтов в строке, поэтому я непоследовательно терял символы. Я изменил его, чтобы получить счетчик байтов.

var endTag = "</Errors>";
var nodeText = GetNodeText();
using (FileStream file = File.Open("my.log", FileMode.Open, FileAccess.ReadWrite))
{
    file.BaseStream.Seek(-(Encoding.UTF8.GetByteCount(endTag)), SeekOrigin.End);
    fileStream.Write(Encoding.UTF8.GetBytes(nodeText), 0, Encoding.UTF8.GetByteCount(nodeText));
    fileStream.Write(Encoding.UTF8.GetBytes(endTag), 0, Encoding.UTF8.GetByteCount(endTag));
}
0 голосов
/ 11 мая 2009

Использование строковых методов (например, поиск в конце файла и затем перемещение назад на длину закрывающего тега) уязвимо для неожиданных, но совершенно законных изменений в структуре документа.

Документ может заканчиваться любым количеством пробелов, чтобы выбрать наиболее вероятную проблему, с которой вы столкнетесь. Это может также заканчиваться любым количеством комментариев или инструкций по обработке. А что произойдет, если элемент верхнего уровня не будет назван Error?

А вот ситуация, в которой при манипулировании строками не удается обнаружить:

<Error xmlns="not_your_namespace">
   ...
</Error>

Если вы используете XmlReader для обработки XML, хотя это может быть не так быстро, как поиск EOF, это также позволит вам обработать все эти возможные условия исключения.

0 голосов
/ 11 мая 2009

Вот как это сделать в C, .NET должен быть похожим.

Игра состоит в том, чтобы просто перейти к концу файла, вернуться назад к тегу, добавить новую строку ошибки и написать новый тег.

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(int argc, char** argv) {
        FILE *f;

        // Open the file
        f = fopen("log.xml", "r+");

        // Small buffer to determine length of \n (1 on Unix, 2 on PC)
        // You could always simply hard code this if you don't plan on 
        // porting to Unix.
        char nlbuf[10];
        sprintf(nlbuf, "\n");

        // How long is our end tag?
        long offset = strlen("</Errors>");

        // Add in an \n char.
        offset += strlen(nlbuf);

        // Seek to the END OF FILE, and then GO BACK the end tag and newline
        // so we use a NEGATIVE offset.
        fseek(f, offset * -1, SEEK_END);

        // Print out your new error line
        fprintf(f, "<Error>New error line</Error>\n");

        // Print out new ending tag.
        fprintf(f, "</Errors>\n");

        // Close and you're done
        fclose(f);
}
0 голосов
/ 11 мая 2009

Как ваш XML-файл представлен в коде? Используете ли вы System.XML-классы? В этом случае вы можете использовать XMLDocument.AppendChild.

0 голосов
/ 11 мая 2009

Самый быстрый способ - это чтение файла с использованием XmlReader и простая репликация каждого узла чтения в новый поток с использованием XmlWriter. точка, в которой вы сталкиваетесь с закрывающим тегом </Errors>, тогда вам просто нужно вывести ваш дополнительный элемент <Error> перед продолжением цикла чтения и дублирования. Этот путь неизбежно будет сложнее, чем чтение всего документа в DOM (класс XmlDocument), но для больших файлов XML, намного быстрее. По общему признанию, использование StreamReader / StreamWriter было бы все еще несколько быстрее, но довольно ужасно работать с кодом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...