Как прочитать большой файл в php без ограничения памяти

#php #file #memory-limit

#php #файл #ограничение памяти

Вопрос:

Я пытаюсь прочитать файл построчно. Проблема в том, что файл был слишком большим (более 500000 строк), и я достигаю предела памяти. Интересно, как прочитать файл без ограничения памяти.

Я думаю о многопоточном решении (например, разделить файл на меньшую группу (100000 строк на группу) и прочитать его в нескольких потоках), но я не знаю, как это сделать подробно. Пожалуйста, помогите мне (извините за плохой английский).

Вот мой код

 $fn = fopen("myfile.txt", "r");

while(!feof($fn)) {
    $result = fgets($fn);
    echo $result;
}

fclose($fn);
  

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

1. ini_set('memory_limit', '512M') ?

2. если вы выводите, отключите буферизацию вывода.

3. Поскольку вы читаете построчно и нигде не сохраняете строки, код в том виде, в каком вы его опубликовали, должен использовать ровно столько памяти, сколько самая длинная строка в вашем файле. Где вы достигаете предела памяти?

4. @Joni Когда чтение строки завершено, я сохраняю данные этой строки в DB

5. «Когда чтение строки завершено, я сохраняю данные этой строки в DB», тогда вы должны показать нам этот код.

Ответ №1:

Вы могли бы использовать генератор для обработки использования памяти. Это всего лишь пример, написанный пользователем на странице документации:

 function getLines($file)
{
    $f = fopen($file, 'r');

    try {
        while ($line = fgets($f)) {
            yield $line;
        }
    } finally {
        fclose($f);
    }
}

foreach (getLines("file.txt") as $n => $line) {
    // insert the line into db or do whatever you want with it.
}
  

Генератор позволяет вам писать код, который использует foreach для перебора набора данных без необходимости создавать массив в памяти, что может привести к превышению лимита памяти или потребовать значительного времени обработки для генерации. Вместо этого вы можете написать функцию-генератор, которая ничем не отличается от обычной функции, за исключением того, что вместо однократного возврата генератор может выдавать столько раз, сколько ему нужно, чтобы предоставить значения для повторения.

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

1. Потрясающий ответ. Не понимал, что yield теперь есть в PHP. Начиная с 5.5 даже… любопытно, что я никогда не видел, чтобы это использовалось до сих пор. Я действительно должен прочитать заметки об обновлении…

2. @Tschallacka не беспокойтесь, я также не видел, чтобы он использовался до нескольких недель назад. Как только я увидел это и прочитал об этом, я был просто поражен. Его можно было бы использовать очень часто, чтобы преодолеть слишком большое использование памяти.

3. прямо сейчас все случаи, когда я писал сложный код, чтобы обойти ограничение памяти, и эта штука была там все время… Я хотел бы, чтобы в php был метод a yield, но я никогда не проверял, был ли он… Я мог бы пнуть себя за то, что не проверил это. Хотел бы я проголосовать за это дважды.

4. Я знал об этом некоторое время, но я не понимаю, как это помогает в данном случае, поскольку единственное, что есть в памяти, — это одна строка, что имеет место даже с этим генератором.

5. Нет ли необходимости обрезать строки? Или как он распознает, когда начинается строка и заканчивается строка? «fgets» обрезает строки для вас?

Ответ №2:

По моему опыту, PHP лучше всего очищает память, когда область действия очищена. Цикл не считается областью видимости, но функция считается.
Таким образом, передача вашего указателя на файл функции, выполнение ваших действий с базой данных внутри функции, а затем выход из функции для цикла, который вы можете вызвать gc_collect_cycles() , должны помочь с управлением вашей памятью и заставить php убирать за собой.

Я также рекомендую отключить echo, а лучше войти в файл. Затем вы можете использовать команду tail -f filename для чтения этого вывода журнала (подсистема Windows Linux, git для Windows bash или в Linux)

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

 function dostuff($fn) 
{
    $result = fgets($fn);
    // store database, do transforms, whatever
    echo $result;
}

$fn = fopen("myfile.txt", "r");

while(!feof($fn)) {
    dostuff($fn);
    flush(); // only need this if you do the echo thing.
    gc_collect_cycles();
}

fclose($fn);
  

Ответ №3:

Вы можете использовать readfile и ob_get_level для управления памятью и буферизацией вывода.

readfile () не будет представлять никаких проблем с памятью, даже при отправке больших файлов, сама по себе. Если вы столкнулись с ошибкой нехватки памяти, убедитесь, что буферизация вывода отключена с помощью ob_get_level().

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

Проверьте это с помощью:

Вы можете использовать следующее в начале вашего скрипта, чтобы остановить буферизацию вывода, если она уже запущена:

 if (ob_get_level()) {
  ob_end_clean();
}
  

Ответ №4:

Вы можете установить ограничение памяти следующим образом ini_set('memory_limit',-1) ;// Ваш скрипт не будет остановлен, пока не завершит чтение. но это неправильный способ, потому что это отнимает время загрузки вашего процессора на сервере.

Лучше разделить файл на куски,

преобразуйте данные вашего файла в массив, после чего вы сможете легко читать его по частям, например

 $file_lines = file('mytext.txt');
foreach ($file_lines as $line) {
    echo $line;
}
  

$file_lines — это ваш массив.