Как прочитать большой файл в PHP?

Dmitriy Nyu
Dmitriy Nyu
Published in
3 min readSep 27, 2016
read_big_files

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

Давайте определимся — что такое большой файл? На мой взгляд, большой файл, это файл такого размера, который не может целиком уместиться в рабочую оперативную память php процесса. Мы не можем просто взять и разместить всё содержимое в строковую переменную, потому что поймаем ошибку «Fatal error: Allowed memory size of XXX bytes exhausted».

Раз нельзя прочесть файл целиком, то надо его прочитать по частям. Есть функция fgets() или более гибкий вариант stream_get_line. Но если мы не знаем формата файла и не уверены, что там есть какие-либо обозначения новой строчки или форматирование, нам придется читать кусками фиксированный длины с помощью функции fread().

Принцип простой — нам нужно два механизма. Первый должен считывать текст по кусочкам из файла. Второй должен принимать эти кусочки и обрабатывать их. В этом посте речь идёт о первом механизме. Для удобства я создал класс, который реализует интерфейс SeekableIterator, что позволяет прочитать файл таким образом:

// Размер куска - 128 байт
$chunkSize = 128;
// Создаем объект-считыватель
$reader = new ChunkedFileReader($path, $chunkSize);
// Наш второй объект-обработчик кусков
$chunkParser = new MyCustomChunkParserThatKnowsWhatToDoWithChunks();
foreach($reader as $chunkPosition => $chunk) {
// кусок за куском обрабатываем файл, пока не кончится файл
$chunkParser->process($chunk)
}

Теперь о первом подводном камне — класс для реализации интерфейса использует функцию fseek(). Функция устанавливает курсор (указатель) на нужную позицию, чтобы начать считывать байты с нужной позиции. Но она перестает работать, когда позиция превышает внутреннюю константу PHP_INT_MAX, на 32-битной установке PHP (и на 64-битных версиях для Windows, которые внутри используют 32-битные целые числа), эта константа практически равна количеству байтов в двух гигабайтах. Поэтому чтобы нормально работать с большими файлами, PHP должен быть скомпилирован с поддержкой 64-битных целых чисел.

Второй подводный камень — это скорость чтения с диска. При чтении большого файла диск будет загружен большим количеством операций чтения (и возможно записи, если в процессе обработки кусков будет эта операция) и это может продолжаться довольно долго. Это может привести к проблемам в работе других процессов, поэтому такое чтение следует совершать на диске, где нет других процессов, например, на специально выделенном хранилище для обработки логов. В идеале, следует продумать всю связку софта и железа заранее. Если вам нужно делать записи в базу, вероятно, лучше разместить её на другом диске или даже сервере, стоит продумать над величиной считываемого куска, чтобы уменьшить количество операций, возможно даже стоит сделать трех-ступенчатую обработку файла: считать куски, совершить легкую подготовку и сохранить данные и уже асинхронно провести окончательную обработку информации. Вроде такая простая задача, а сложностей может возникнуть огромное количество.

Вот мой пакет, о котором я писал выше: https://github.com/DmitriyNyu/chunked-file-reader, так же можно поискать и другие решения, в том числе заточенные под конкретные фреймворки.

--

--