Страницы

Поиск по вопросам

воскресенье, 2 февраля 2020 г.

Порционное чтение большого Xml файла с известной структурой

#c_sharp #xml


Есть файл xml-файл вида:



  
    test.log
    C:/
    C:/copy/
  
  
    Win8.iso
    C:/
    C:/copy/
  
  
    TemplateNew.xlsx
    C:/
    C:/copy/
  
  ...
  ...
  ...



под тегами скрыто: имя файла, исходный путь к файлу и путь, по которому данный файл
требуется скопировать.

Решил парсить файл в список структур:

var document = new XmlDocument();
        document.Load(configPath);

        XmlNode root = document.DocumentElement;

        string name = "";
        string source = "";
        string target = "";
        int iCount = 0;

        foreach (XmlNode nodes in root.ChildNodes)
        {                              
            foreach (XmlNode tagname in nodes.ChildNodes)
            {
                if (tagname.Name == "name")
                {
                    name = tagname.InnerText;
                }
                if (tagname.Name == "sourcePath")
                {
                    source = tagname.InnerText;
                }
                if (tagname.Name == "targetPath")
                {
                    target = tagname.InnerText;
                }
            }
            collect.Added(name, source, target);
            iCount++;
        }

        Console.WriteLine("заполнено {0} элементов", iCount);


Но вот загвоздка, если предположить что файл будет размером, близким к бесконечности,
то список превысит допустимые размеры оперативной памяти и я словлю исключение о её
чрезмерном размере. 
Каким образом можно организовать порционное чтение данных, скажем, по 100 записей? 
Подходит ли данный способ чтения файла под эту задачу?
    


Ответы

Ответ 1



Нужно использовать потоковый XmlReader. Загружать сразу все данные в коллекцию нельзя, т. к. по условию файл потенциально очень большой. Будем читать по 100 записей. Создадим класс для данных: class File { public string Name { get; set; } public string SourcePath { get; set; } public string TargetPath { get; set; } } Метод, возвращающий список с указанным количеством записей (или меньше в самом конце): IEnumerable> ReadFiles() { using (var reader = XmlReader.Create(fileName)) { var files = new List(); while (reader.ReadToFollowing("name")) { var file = new File(); file.Name = reader.ReadElementContentAsString(); reader.MoveToContent(); file.SourcePath = reader.ReadElementContentAsString(); reader.MoveToContent(); file.TargetPath = reader.ReadElementContentAsString(); files.Add(file); if (files.Count % 100 == 0) { yield return files; files = new List(); } } if (files.Count > 0) yield return files; } } Использование этого метода: foreach (var files in ReadFiles()) { // работаем с коллекцией files foreach(var file in files) { // работаем с одним экземпляром file } }

Ответ 2



Вариант с ленивым чтением, xml не будет загружаться в память полностью, как в вашем случае, т.е. можно сделать поточную обработку, можно читать и пачками, если написать Extension, который читает IEnumerable<> и разбивает его на порции заданного размера IEnumerable> public class FileModel { public string Name { get; set;} public string SourcePath { get; set; } public string TargetPath { get; set; } } public static IEnumerable ReadXml(string path) { using (var fileStream = File.OpenRead(path)) using (var streamReader = new StreamReader(fileStream, Encoding.UTF8)) using (var reader = XmlReader.Create(streamReader)) { while (reader.Read()) { if (reader.NodeType != XmlNodeType.Element || reader.Name != "file") { continue; } XElement el = XElement.ReadFrom(reader) as XElement; var fileModel = new FileModel { Name = (string)el.Element("name"), SourcePath = (string)el.Element("sourcePath"), TargetPath = (string)el.Element("targetPath"), }; yield return fileModel; } } }

Комментариев нет:

Отправить комментарий