Как работать с большими (100гб) txt файлами?

  • Автор темы Автор темы BAZAg
  • Дата начала Дата начала

BAZAg

Client
Регистрация
08.11.2015
Сообщения
2 394
Реакции
2 801
Баллы
113
Есть на первый взгляд простая задача - взять случайную строчку с файла скажем 10 раз по 10 строк.
Сам файл большой - из-за чего загрузить его в ОЗУ нельзя (да и в многопотоке - каждый поток если подгрузит себе такое добро - печалька будет).

Как бы Вы решали эту проблему?

На коленке, очевидным для меня является решение примерно такое:
C#:
Развернуть Свернуть Копировать
Random rand = new Random();
string path = @"C:\file.txt";

IEnumerable<string> lines = File.ReadLines(path).Where(x=>!string.IsNullOrEmpty(x)); // Задача на чтение файла без загрузки в ОЗУ всего файла
int count = lines.Count(); // Считаю количество строчек без загрузки всего файла в ОЗУ
for(int j=0;j<10;j++){
    for(int i = 0;i<10;i++)
        project.SendInfoToLog(lines.ElementAt(rand.Next(count))); // Получаю нужную строчку (в ОЗУ улетает только выбранная строчка)
}

Как можно более лаконично/правильно реализовать подобный функционал?

P.S. Смущает меня, что int count = lines.Count(); может выходить за пределы максимально допустимых значений...
И в целом, есть сомнение на счёт что произойдет если два потока будут читать в "ленивом" режиме один и тот же файл.
Также lines.ElementAt(1); принимает int и что делать, если количество элементов выходит за пределы int и нужно взять такой номер элемента?
 
Последнее редактирование:
Я бы предварительно подготовил исходный файл, разбил на более менее "приятные" куски.
 
  • Оценить
Реакции: BAZAg
Либо нарезать, м.б. юзать FileStream. Суть в том, что бы по кусочку в буфер брать часть файла, делать что требуется и брать следующий кусок.
 
  • Оценить
Реакции: BAZAg


Я бы смотрел в сторону первичных инструментов.

C#:
Развернуть Свернуть Копировать
string line = string.Empty;
int64 i = 0;

using (var stream = File.Open(myFilePath, System.IO.FileMode.Open, FileAccess.Read))
using (var reader = new StreamReader(stream))
{
    while (!reader.EndOfStream) i++;
    
    // Дальше генерируешь случайные совпадения и получаешь их через
    line = reader.ReadLine().ElementAt(7);
    
    // Если необходимо можешь добавить проверку на пустую строку
    if (line.Length != 0 && !line.Equals("\n", StringComparison.Ordinal))
    {
        
    }
}
 
  • Оценить
Реакции: volody00, Alexmd и BAZAg
Была такая задача, работа именно с ТХТ
советую следующее
1. нарезать файлы не более 100мб - вышел опытным путем, при таком +- размере, сервак работает идеально быстро
2. когда будет много файлов, а их будет много, обязательно разделять по папка, типа по 10-20 файлов в папке, это так же работает на скорость работы, когда они все будут в одной папке, будет тупить +-

ну а так наверно лучше sql юзать
 
  • Оценить
Реакции: one и BAZAg
писал для себя шаблон по разбивке на более мелкие фалы
 
  • Оценить
Реакции: BAZAg
Буквально вчера столкнулся с похожей задачей. Печалька случалась пару раз, вплоть до перезагрузки системы)))) Собрал текстовый файл на 90ГБ и решил в нем поковыряться зенкой(придумал предварительно алгоритмы по очистке строк от мусора, дублей и сортировке). Думаю, машина то у меня мощная. В общем, бился часа 3 с этим чудовищем. Плюнул. Оставил до лучших времен.
Попробовал применить подход Юрия сегодня - просто, чтоб пощупать обращение к файлу такого размера. Строки доставались с периодичностью секунд 10 каждая. Хрень какая-то, но хоть не зависает.
Ошибки с номером строки не возникнет в процессе работы, так как зенка поругается на это еще на уровне компиляции. Проверить это можно, запустив такой код return new Random().Next(2*int.MaxValue);, да и 2KKK+ строк должны быть слишком малоинформативными для такого веса.

В общем, я сам себе придумал проблему, объединив все в один файл нахрапом. Если есть возможность, работайте с меньшими файлами.

Пока писал этот ответ, пришла мысль, как оптимизировать забор строк - несколько сумбурно опишу, но все же, думаю поймете. Метод кажется мне крайне логичным:
предположим, что Вы уже разбили большой файл или не соединяли маленькие в этого "текстового переростка", так вот, надо организовать из них цепочку. Берем все маленькие файлы, подгоняем их под единый приемлемый размер и забираем их пути в список. Далее остается только составить корректную математическую формулу подсчета. Наглядно:
1000 файлов по 2.000.000 строк вместо "бугая" на 2 миллиарда.
получаем рандомное число, скажем 1.400.322.167
высчитываем быстренько интами 1400322167-1400322167/2000000 и получаем, что нам надо обратиться к файлу с индексом 700 в списке файлов и взять из него строку 322167, что явно(даже не требует тестирования) будет быстрее моих утренних 10 секунд и впишется в миллисекунды.
с индексами, наверняка напутал +-1, но, думаю, суть донес.
Как разбить такой большой файл пока даже не думаю - все. я спать.
ps ну, а так, лучше бигдата заюзать
 
  • Оценить
Реакции: Tvister и BAZAg
EmEditor
бесплатный период есть
Если задача постоянная - проще купить
Ну это если будет нормально работать
Кеи что в сети - нерабочие
 
  • Оценить
Реакции: BAZAg
Столкнулся с тем что Content Downloader при загрузке больше 20т. строк у меня подвисает. Собрал шаблон на кубиках за 10 минут. Делит 1 файл на несколько файлов по 15т. строк и сохраняет в один каталог с именами: имя файла+1 т.д. В чем вопрос то?
 
  • Оценить
Реакции: BAZAg
Категорически советую редактор - EmEditor
он очень легко обращается в огромными файлами, на торентах есть крякнутые версии.
Так же в нем есть функция разбива на файлы

Да notepad++ не так крут как EmEditor
я щас только им и пользуюсь, умеет реально все
 
  • Оценить
Реакции: bigloafer и BAZAg
А так что б без стороннего по можно как то реализовать ??
 
А так что б без стороннего по можно как то реализовать ??

Для своих задач я использовал примерно так:
C#:
Развернуть Свернуть Копировать
string path = "file.txt";
string path2 = "file2.txt";
string line = File.ReadLines(path).First();
File.WriteAllLines(path2 , File.ReadLines(path).Skip(1));

А для того, чтобы брать случайную строчку - просто сначала узнавал размер файла, потом с помощью рандома выбрасывал случайное число, после чего считывал строку вот так примерно, где вместо 1 номер случайной строки:
C#:
Развернуть Свернуть Копировать
string line = File.ReadLines(path).Skip(1).First();

Но, чётко для себя уяснил, что брать строки с удалением с большого файла - это плохая идея.
Из-за чего в основном с большого файла просто читал строчки.
И если нужно было их удалять - то я просто вел список номеров строк, которые использованы, и когда уже накапливалось 10 000 строчек, тогда просто один раз проходил в цикле по этому большому файлу, и строчки с номерами из черного списка просто не записывал (пропускал) - все остальное записывал в другой файл. После чего, новый образец сохранял, старый удалял.

Какая-то такая примерно логика у меня была.
 
умно но неуместно .. спасибо ...
а почему не уместно? Вы разве не пользуетесь сторонними программами вообще?
Можно заменить или добавить текстовый редактор, как вспомогательный, например он у меня так и сделан, не основной.
Он очень простой и очень удобный, и легко работает с огромными файлами, для этого его и создали, глупо не воспользоваться, потратив 10 минут до результата.
 
а почему не уместно? Вы разве не пользуетесь сторонними программами вообще?
Можно заменить или добавить текстовый редактор, как вспомогательный, например он у меня так и сделан, не основной.
Он очень простой и очень удобный, и легко работает с огромными файлами, для этого его и создали, глупо не воспользоваться, потратив 10 минут до результата.
суть зенопостера в автоматизации .... а если ты добавляешь стороний софт то это полуавтомат уже ... за которым тебе надо следить ....
 
так речь про то чтоб подготовить огромный файл или каждый день обрабатывать разные большие файлы?
У человека файл меньше 2 миллиардов строк (за пределы int.MaxValue не выходит).
Другими словами, задача не о 100 гб.


В многопоточном режиме из списками Зеннопостер работало, но кушало около 60 гб оперативной памяти.
После преобразования все работает корректно в многопоточном режиме и не кушает ОЗУ как раньше.

C#:
Развернуть Свернуть Копировать
string path_counter = Path.Combine(project.Directory, "counter.txt");
string path = Path.Combine(project.Directory, "file.txt");
string line = string.Empty;

lock(SyncObjects.ListSyncer) {
    try {
        string num = File.ReadAllText(path_counter).Trim();
        if(string.IsNullOrEmpty(num)) num = "0";
        
        int num_line = 0;
        if(int.TryParse(num, out num_line)) {
            num_line++;
            if(num_line < File.ReadLines(path).Count()) {
                line = File.ReadLines(path).Skip(num_line).First();
                File.WriteAllText(path_counter, num_line.ToString());
            }
            else {
                throw new Exception("Дошли до конца файла");
            }
        }
        else {
            project.SendWarningToLog("В файле содержащему счётчик не число", true);
            throw new Exception("В файле содержащему счётчик не число");
        }
    }
    catch (Exception e) {
        project.SendWarningToLog(e.Message, true);
        throw new Exception(e.Message);
    }
}
return line;

Ещё один подобный вариант я использовал, где хранил данные в json и старался избегать File.ReadLines(path).Count(), хранил его рядом с номером строки, которую получал.

C#:
Развернуть Свернуть Копировать
string path_counter = Path.Combine(project.Directory, "counter.txt");
string path = Path.Combine(project.Directory, "file.txt");
string line = string.Empty;

lock(SyncObjects.ListSyncer) {
    bool check_f1 = File.Exists(path_counter);
    bool check_f2 = File.Exists(path);
    if(!check_f2) throw new Exception("нет: " +path);

    var dic = new Dictionary<string, int>();
    if(!check_f1) {
        dic["row"] = 0;
        dic["max"] = File.ReadLines(path).Count();
    }
    else {
        try {
            dic =  Global.ZennoLab.Json.JsonConvert.DeserializeObject<Dictionary<string, int>>(File.ReadAllText(path_counter).Trim());
        }
        catch {
            dic["row"] = 0;
            dic["max"] = File.ReadLines(path).Count();
        }
    }
    
    if(!(dic["max"] > dic["row"])) {   
        dic["row"] = 0;
        dic["max"] = File.ReadLines(path).Count();
    }
    try {
        line = File.ReadLines(path).Skip(dic["row"]).First();
        dic["row"]++;
        File.WriteAllText(path_counter, Global.ZennoLab.Json.JsonConvert.SerializeObject(dic,  Global.ZennoLab.Json.Formatting.Indented));
    }
    catch (Exception e) {
        project.SendWarningToLog(e.Message, true);       
         dic["max"] = File.ReadLines(path).Count();
        File.WriteAllText(path_counter, Global.ZennoLab.Json.JsonConvert.SerializeObject(dic,  Global.ZennoLab.Json.Formatting.Indented));
        throw new Exception(e.Message);
    }
}

return line;

А относительно файлов размером в 100 гигабайт - пока лучшим решением есть нарезание файлов, как об этом говорил Alexmd - ничего проще мне найти не получилось. Когда количество строк вылетает за пределы int - совсем не ясно как с этим что-либо делать, когда ни Skip ни Count с этим не работает...
 
С большими файлами, я работал так, брал файл, разбивал его на части по N строк, максимально быстро файлы обрабатывались при размере 50-60Мб (это примерно 2-3кк строк)
все это файлы записывал в отдельную папку и привязывал к списку, если список заканчивался, перепривязывался следующий и так далее.
Поэтому и советую редактор, у него есть функция разбивки на файлы определенного размера(количества строк)
 
С большими файлами, я работал так, брал файл, разбивал его на части по N строк, максимально быстро файлы обрабатывались при размере 50-60Мб (это примерно 2-3кк строк)
все это файлы записывал в отдельную папку и привязывал к списку, если список заканчивался, перепривязывался следующий и так далее.
Поэтому и советую редактор, у него есть функция разбивки на файлы определенного размера(количества строк)
Надо как-то измерять разницу в производительности.
Есть код, который режет большие файлы, на файлы указанного размера, например тут:

Если разница во времени примерно одинаковая - то резать их в Зеннопостере должно быть комфортнее.
На крайний случай упаковать в отдельный шаблон, и подавать на вход только путь к большому файлу и количество строчек.
 

Кто просматривает тему: (Всего: 0, Пользователи: 0, Гости: 0)