Время прочтения: 5 мин.

Сейчас такое время, что SQL уже считается «старым и добрым». И тем не менее, бывают такие случаи, что возникает необходимость работать на еще более глубоком уровне архаики – а именно, обрабатывать текстовые файлы. Но не просто текстовые файлы, а гигантские текстовые файлы. К примеру, из файлов общим размером 10 Гб и 10 млн. строк «выдернуть» строки, содержащие определенный набор образцов, например, конкретные номера счетов или ФИО. Откуда берутся такие файлы, спросите вы. Самый простой пример – выгрузки из Баз данных АС организации или выгрузки с Интернет-ресурсов; причем выгрузки такие, что проще сначала «выгрузить все», а потом уже разбираться, что нам действительно нужно, а что нет.

Конечно, можно импортировать все в БД SQL и обработать там, но есть ещё один способ решения данной задачи.

В операционной системе Windows есть встроенная утилита findstr.exe, которая как раз выполняет подобную задачу. Утилита, естественно, запускается из командной строки (CMD). В самом простом варианте команда выглядит так:

findstr.exe big_file.txt    /G:samples.txt >rezultat.txt

Где:

big_file.txt – тот самый «большой файл», из которого надо выдернуть строки;

samples.txt – файл с образцами поиска;

rezultat.txt – файл с результатом («выдернутыми» строками).

Вроде все хорошо, но есть некоторые нюансы, которые сказываются на конечном результате. Утилита findstr плохо работает с кириллицей. Тесты показали, что findstr «пропускает/не находит» до 17% строк, содержащих заданные образцы с кириллическими буквами. Кроме того, на больших массивах данных быстродействие findstr оставляет желать лучшего.

Что же делать? Использовать утилиту fs.exe собственной разработки, аналогичную findstr. Утилита fs.exe реализует тот же самый функционал, что и findstr (за исключением регулярных выражений), имеет несравнимо лучшие характеристики по быстродействию, не имеет проблем с кириллицей, а взамен отсутствующего функционала регулярных выражений имеет несколько полезных фич, которых нет у findstr. На что еще надо обратить внимание – у fs.exe результирующий файл задается не символом «>», а ключом    /out: .

Приведенный ниже сравнительный анализ быстродействия обеих утилит проводился на «большом файле» размером 780 Мб, содержащем 10 млн. строк. Осуществлялся многократный прогон утилит с одними и теми же параметрами, после чего определялось время отработки как разницы между временем создания результирующего файла и временем его последней модификации (с округлением до целого числа секунд).

Кол-во образцов поискаЗатраченное время, секЗатраченное время, сек
 fs.exefindstr.exe
144
10514
1005125
100051274
10000711991
1000008#Н/Д
100000021 (секунд!)#Н/Д (Страшно подумать, но если исходить из сохранения пропорций – то получается 14 дней)

Преимущество в пользу fs.exe очевидно. За счет чего достигнут такое преимущество? Предполагаем, что в findstr.exe реализовано сравнение подстроки с образцами последовательным перебором. В fs.exe же реализован бинарный поиск, т.е. для того, чтобы сравнить подстроку с миллионом образцов, требуется только 20 сравнений (двоичный логарифм от миллиона).

Насчет полезных фич. Если знать, что диапазон поиска – не вся строка, а только 50 символов, начиная с 100-го символа, то можно сообщить об этом утилите:

fs.exe big_file.txt    /G:samples.txt    /npos:100:50    /out:rezultat.txt

А можно просто исключить из поиска первые сто символов строки:

fs.exe big_file.txt    /G:samples.txt    /npos:100    /out:rezultat.txt

В любом случае время отработки так или иначе сократится.

Можно искать образцы, игнорируя все   /любые пробелы, как в образцах, так и строках «Большого файла»:

fs.exe big_file.txt    /G:samples.txt    /ns    /out:rezultat.txt

Фича родилась, когда надо было по ограниченному списку данных профильтровать в выгрузке. Эти данные выводились в формате XXXXX XXX X XXXX XXXXXXX, т.е. с разделяющими пробелами.

Можно вывести в результирующий файл не только строки «Большого файла», но и сами образцы, найденные в этих строках:

fs.exe big_file.txt    /G:samples.txt    /prsmpl    /out:rezultat.txt

Зачем это нужно? Бывает, что вопреки ожиданиям, утилита выдает в результат все строки исходного файла, в то время как ожидаемый результат должен быть гораздо меньше. Такое может произойти, когда количество образцов достаточно большое, и в это множество образцов каким-то образом затесался очень короткий образец. Например, у нас есть список образцов из 10 000 ФИО, а где-то в середине списка – строка, состоящая из одного символа «А». Вполне понятно, что эта «А» «вытянет» строку практически с любым ФИО. Данная фича поможет определить, какой именно образец является лишним.

Бывают и такие ситуации, когда образцы для поиска находятся внутри большого файла CSV-формата в каком-то отдельном столбце. Можно, конечно, открыть этот файл в EXCEL, скопировать оттуда необходимый столбец и вставить скопированное в текстовый файл. Но можно обойтись и меньшим количеством действий, сообщив утилите, что строки файла samples.txt надо предварительно разделить на отдельные поля по символу «;» (разделителем полей могут быть любые символы — один или несколько. Надо только указать эти символы в подключе    /SEP:), и взять в качестве образца только четвертое поле (  /COL:4):

fs.exe big_file.txt    /G:samples.txt   /SEP:;   /COL:4    /out:rezultat.txt

На самом деле, ключей у утилиты fs.exe гораздо больше, и узнать про них можно, запустив утилиту без аргументов.

И в заключении немного истории. Разработка утилита fs.exe стартовала где-то году в 2016 году. Причиной принятия решения о разработке стали:

  1. Постоянная необходимость фильтрации больших текстовых массивов (выгрузок из АС);
  2. Вышеупомянутое низкое быстродействие и не полная совместимость с кириллицей утилиты findstr.

В то же время, функциональность утилиты findstr вполне устраивала, поэтому в качестве шаблона для разработки была выбрана она.

В то время о наличии Visual Studio о рабочей станции даже речи не могло быть, поэтому утилита писалась на языке C++, а для создания исполняемого модуля использовался компилятор Borland C++ Compiler 5.5.

Ссылка на код программы.