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

BAZAg

Client
Регистрация
08.11.2015
Сообщения
1 786
Благодарностей
2 450
Баллы
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 и нужно взять такой номер элемента?
 
Последнее редактирование:

one

Client
Регистрация
22.09.2015
Сообщения
6 831
Благодарностей
1 275
Баллы
113
Я бы предварительно подготовил исходный файл, разбил на более менее "приятные" куски.
 
  • Спасибо
Реакции: BAZAg

doc

Client
Регистрация
30.03.2012
Сообщения
8 685
Благодарностей
4 641
Баллы
113
я бы тоже просто нарезал бы, и оттуда уже рандомил и файл и строку
 
  • Спасибо
Реакции: BAZAg

Yuriy Zymlex

Moderator
Команда форума
Регистрация
24.10.2016
Сообщения
6 498
Благодарностей
3 364
Баллы
113
Либо нарезать, м.б. юзать FileStream. Суть в том, что бы по кусочку в буфер брать часть файла, делать что требуется и брать следующий кусок.
 
  • Спасибо
Реакции: BAZAg

RoyalBank

Client
Регистрация
07.09.2015
Сообщения
557
Благодарностей
550
Баллы
93

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

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

backoff

Client
Регистрация
20.04.2015
Сообщения
6 039
Благодарностей
6 470
Баллы
113
Была такая задача, работа именно с ТХТ
советую следующее
1. нарезать файлы не более 100мб - вышел опытным путем, при таком +- размере, сервак работает идеально быстро
2. когда будет много файлов, а их будет много, обязательно разделять по папка, типа по 10-20 файлов в папке, это так же работает на скорость работы, когда они все будут в одной папке, будет тупить +-

ну а так наверно лучше sql юзать
 
  • Спасибо
Реакции: one и BAZAg

max_linder

Client
Регистрация
24.09.2019
Сообщения
13
Благодарностей
9
Баллы
3
писал для себя шаблон по разбивке на более мелкие фалы
 
  • Спасибо
Реакции: BAZAg

Yuriy Zymlex

Moderator
Команда форума
Регистрация
24.10.2016
Сообщения
6 498
Благодарностей
3 364
Баллы
113
  • Спасибо
Реакции: BAZAg

Alexmd

Client
Регистрация
10.12.2018
Сообщения
1 022
Благодарностей
1 424
Баллы
113
Буквально вчера столкнулся с похожей задачей. Печалька случалась пару раз, вплоть до перезагрузки системы)))) Собрал текстовый файл на 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

uuw

Client
Регистрация
04.06.2020
Сообщения
146
Благодарностей
54
Баллы
28
EmEditor
бесплатный период есть
Если задача постоянная - проще купить
Ну это если будет нормально работать
Кеи что в сети - нерабочие
 
  • Спасибо
Реакции: BAZAg

max_linder

Client
Регистрация
24.09.2019
Сообщения
13
Благодарностей
9
Баллы
3
Столкнулся с тем что Content Downloader при загрузке больше 20т. строк у меня подвисает. Собрал шаблон на кубиках за 10 минут. Делит 1 файл на несколько файлов по 15т. строк и сохраняет в один каталог с именами: имя файла+1 т.д. В чем вопрос то?
 
  • Спасибо
Реакции: BAZAg

backoff

Client
Регистрация
20.04.2015
Сообщения
6 039
Благодарностей
6 470
Баллы
113
Категорически советую редактор - EmEditor
он очень легко обращается в огромными файлами, на торентах есть крякнутые версии.
Так же в нем есть функция разбива на файлы

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

xatchikzzz

Client
Регистрация
08.09.2010
Сообщения
574
Благодарностей
41
Баллы
28
А так что б без стороннего по можно как то реализовать ??
 

backoff

Client
Регистрация
20.04.2015
Сообщения
6 039
Благодарностей
6 470
Баллы
113
Можно распечатать на принтере, а потом руками набрать в текстовик, ну это при условии что принтер откроет
 
  • Спасибо
Реакции: Devostator и n0n3mi1y

xatchikzzz

Client
Регистрация
08.09.2010
Сообщения
574
Благодарностей
41
Баллы
28

Sherminator

Client
Регистрация
10.09.2021
Сообщения
1 238
Благодарностей
671
Баллы
113

xatchikzzz

Client
Регистрация
08.09.2010
Сообщения
574
Благодарностей
41
Баллы
28

BAZAg

Client
Регистрация
08.11.2015
Сообщения
1 786
Благодарностей
2 450
Баллы
113
А так что б без стороннего по можно как то реализовать ??
Для своих задач я использовал примерно так:
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 строчек, тогда просто один раз проходил в цикле по этому большому файлу, и строчки с номерами из черного списка просто не записывал (пропускал) - все остальное записывал в другой файл. После чего, новый образец сохранял, старый удалял.

Какая-то такая примерно логика у меня была.
 

backoff

Client
Регистрация
20.04.2015
Сообщения
6 039
Благодарностей
6 470
Баллы
113
умно но неуместно .. спасибо ...
а почему не уместно? Вы разве не пользуетесь сторонними программами вообще?
Можно заменить или добавить текстовый редактор, как вспомогательный, например он у меня так и сделан, не основной.
Он очень простой и очень удобный, и легко работает с огромными файлами, для этого его и создали, глупо не воспользоваться, потратив 10 минут до результата.
 

xatchikzzz

Client
Регистрация
08.09.2010
Сообщения
574
Благодарностей
41
Баллы
28
а почему не уместно? Вы разве не пользуетесь сторонними программами вообще?
Можно заменить или добавить текстовый редактор, как вспомогательный, например он у меня так и сделан, не основной.
Он очень простой и очень удобный, и легко работает с огромными файлами, для этого его и создали, глупо не воспользоваться, потратив 10 минут до результата.
суть зенопостера в автоматизации .... а если ты добавляешь стороний софт то это полуавтомат уже ... за которым тебе надо следить ....
 

backoff

Client
Регистрация
20.04.2015
Сообщения
6 039
Благодарностей
6 470
Баллы
113
так речь про то чтоб подготовить огромный файл или каждый день обрабатывать разные большие файлы?
 

BAZAg

Client
Регистрация
08.11.2015
Сообщения
1 786
Благодарностей
2 450
Баллы
113
так речь про то чтоб подготовить огромный файл или каждый день обрабатывать разные большие файлы?
У человека файл меньше 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 с этим не работает...
 

backoff

Client
Регистрация
20.04.2015
Сообщения
6 039
Благодарностей
6 470
Баллы
113
С большими файлами, я работал так, брал файл, разбивал его на части по N строк, максимально быстро файлы обрабатывались при размере 50-60Мб (это примерно 2-3кк строк)
все это файлы записывал в отдельную папку и привязывал к списку, если список заканчивался, перепривязывался следующий и так далее.
Поэтому и советую редактор, у него есть функция разбивки на файлы определенного размера(количества строк)
 

BAZAg

Client
Регистрация
08.11.2015
Сообщения
1 786
Благодарностей
2 450
Баллы
113
С большими файлами, я работал так, брал файл, разбивал его на части по N строк, максимально быстро файлы обрабатывались при размере 50-60Мб (это примерно 2-3кк строк)
все это файлы записывал в отдельную папку и привязывал к списку, если список заканчивался, перепривязывался следующий и так далее.
Поэтому и советую редактор, у него есть функция разбивки на файлы определенного размера(количества строк)
Надо как-то измерять разницу в производительности.
Есть код, который режет большие файлы, на файлы указанного размера, например тут:

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

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