Чтение очень больших файлов в PHP

fopen не работает, когда я пытаюсь прочитать файл очень среднего размера в PHP. A 6 meg file заставляет его задыхаться, хотя файлы меньшего размера вокруг 100k вполне подойдут. Я читал, что иногда необходимо перекомпилировать PHP с флагом -D_FILE_OFFSET_BITS=64, чтобы читать файлы размером более 20 гигабайт или что-то нелепое, но разве у меня не должно быть проблем с файлом 6 мегабайт? В конце концов, мы захотим читать файлы размером около 100 мегабайт, и было бы неплохо иметь возможность открывать их, а затем читать их построчно с помощью fgets, как я могу делать с файлами меньшего размера.

Каковы ваши приемы / решения для чтения и выполнения операций с очень большими файлами в PHP?

Обновление: вот пример простого кодового блока, который не работает в моем 6-мегабайтном файле - PHP, похоже, не выдает ошибку, он просто возвращает false. Может я делаю что-то предельно тупое?

$rawfile = "mediumfile.csv";

if($file = fopen($rawfile, "r")){  
  fclose($file);
} else {
  echo "fail!";
}

Еще одно обновление: спасибо всем за вашу помощь, это действительно оказалось чем-то невероятно глупым - проблема с разрешениями. У моего маленького файла необъяснимым образом были права на чтение, а у большого файла - нет. Дох!


person Community    schedule 02.10.2008    source источник
comment
Вы просто пытаетесь передать файл? т.е. Скачать? Или вы действительно для какой-то цели анализируете данные в файлах? Спасибо.   -  person DreamWerx    schedule 02.10.2008
comment
он не должен выходить из строя без выдачи предупреждения / ошибки. Включите все ошибки с помощью error_reporting (E_ALL) и убедитесь, что display_errors включен для отображения в вашем браузере, или проверьте журнал ошибок вашего веб-сервера.   -  person Philip Reynolds    schedule 02.10.2008


Ответы (8)


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

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

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

$handle = fopen("/tmp/uploadfile.txt", "r") or die("Couldn't get handle");
if ($handle) {
    while (!feof($handle)) {
        $buffer = fgets($handle, 4096);
        // Process buffer here..
    }
    fclose($handle);
}

Изменить

Кажется, что PHP не выдает ошибок, он просто возвращает false.

Правильный ли путь к $rawfile относительно того, где запущен скрипт? Возможно, попробуйте установить здесь абсолютный путь для имени файла.

person ConroyP    schedule 02.10.2008
comment
Это единственно возможное решение, как открывать действительно большие файлы. Я обрабатываю этим решением файл размером 1,5 ГБ без каких-либо проблем. Все другие решения, такие как file_get_contents of file, будут читать весь файл в память. Этот подход осуществляется построчно. - person StanleyD; 22.08.2013
comment
Почему 4096 означает одну строку? - person Phoenix; 03.02.2015
comment
@Phoenix 4096 означает, что читать не более 4096 - 1 байт, если не встречаются разрывы строк. Проверьте руководство. - person a3f; 09.02.2015
comment
Для меня stream_get_line быстрее, чем fgets, посмотрите этот сравнительный gist.github.com/joseluisq/6ee3876dc64561ffa14b - person joseluisq; 11.03.2016

Сделал 2 теста с файлом 1,3 ГБ и файлом 9,5 ГБ.

1,3 ГБ

Использование fopen()

Для вычислений этот процесс использовал 15555 мс.

Он потратил 169 мс на системные вызовы.

Использование file()

Для вычислений этот процесс использовал 6983 мс.

На системные вызовы затрачено 4469 мс.

9,5 ГБ

Использование fopen()

Этот процесс использовал для своих вычислений 113559 мс.

На системные вызовы затрачено 2532 мс.

Использование file()

Для вычислений этот процесс использовал 8221 мс.

На системные вызовы затрачено 7998 мс.

Кажется, file() быстрее.

person Al-Punk    schedule 07.10.2015

• Функция fgets() работает до тех пор, пока размер текстовых файлов не превысит 20 МБ, и скорость анализа сильно не снизится.

• Функция file_ get_contents() дает хорошие результаты до 40 МБ и приемлемые результаты до 100 МБ, но file_get_contents() загружает в память весь файл, поэтому она не масштабируется.

• Функция file() губительна для больших файлов текста, потому что эта функция создает массив, содержащий каждую строку текста, таким образом, этот массив хранится в памяти, а используемая память даже больше.
На самом деле, файл размером 200 МБ я мог только удалось выполнить синтаксический анализ с memory_limit, установленным на 2 ГБ, что было неприемлемо для файлов размером 1+ ГБ, которые я намеревался проанализировать.

Когда вам нужно проанализировать файлы размером более 1 ГБ, а время анализа превышает 15 секунд, и вы не хотите загружать весь файл в память, вам нужно найти другой способ.

Мое решение заключалось в том, чтобы анализировать данные произвольными небольшими порциями. Код такой:

$filesize = get_file_size($file);
$fp = @fopen($file, "r");
$chunk_size = (1<<24); // 16MB arbitrary
$position = 0;

// if handle $fp to file was created, go ahead
if ($fp) {
   while(!feof($fp)){
      // move pointer to $position in file
      fseek($fp, $position);

      // take a slice of $chunk_size bytes
      $chunk = fread($fp,$chunk_size);

      // searching the end of last full text line
      $last_lf_pos = strrpos($chunk, "\n");

      // $buffer will contain full lines of text
      // starting from $position to $last_lf_pos
      $buffer = mb_substr($chunk,0,$last_lf_pos);

      ////////////////////////////////////////////////////
      //// ... DO SOMETHING WITH THIS BUFFER HERE ... ////
      ////////////////////////////////////////////////////

      // Move $position
      $position += $last_lf_pos;

      // if remaining is less than $chunk_size, make $chunk_size equal remaining
      if(($position+$chunk_size) > $filesize) $chunk_size = $filesize-$position;
      $buffer = NULL;
   }
   fclose($fp);
}

Используемая память - это только $chunk_size, а скорость немного меньше, чем у file_ get_contents(). Я думаю, что PHP Group должна использовать мой подход, чтобы оптимизировать функции синтаксического анализа.

*) Найдите функцию get_file_size() здесь.

person Tinel Barb    schedule 15.02.2019
comment
Это неполно, fread перемещает указатель файла. Не сбрасывая позицию, вы теряете первый кусок, тоже большой ... 16mb. Сначала тест - person ion; 04.03.2019
comment
Спасибо, Ионут, за полезное наблюдение. Код обновлен. - person Tinel Barb; 16.04.2019
comment
Я пробовал это с большим файлом (около 256 МБ), но кажется, что цикл застревает в последней части буфера. Кажется, что буфер содержит только 1 строку в последней части размером 16 Мбайт, поэтому он считывает каждую строку по сигнатуре, и на ее завершение уходит вечность. - person GerritElbrink; 08.04.2021

Что ж, вы можете попробовать использовать функцию чтения файла, если вы просто хотите вывести файл.

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

person Fionn    schedule 02.10.2008
comment
Нам нужно автоматизировать добавление больших наборов данных, чтобы большие CSV-файлы могли загружаться пользователем, а затем анализироваться и интегрироваться в базу данных приложением. Мне бы хотелось найти другие варианты подхода, если вы считаете, что чтение и анализ загруженных файлов с помощью PHP - не лучший способ. - person ; 02.10.2008
comment
Я бы не подумал, что у PHP возникнут проблемы с CSV-файлами размером 6 МБ? Кажется, это достаточно маленький файл, чтобы его можно было обработать. В соответствии с комментариями выше, пожалуйста, опубликуйте точную ошибку / или код. Может быть ошибка памяти при ударе? Или max_execution_time? Нам нужна дополнительная информация, чтобы помочь. - person DreamWerx; 02.10.2008

Я использовал fopen для открытия видеофайлов для потоковой передачи, используя скрипт php в качестве сервера потокового видео, и у меня не было проблем с файлами размером более 50/60 МБ.

person Enrico Murru    schedule 02.10.2008

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

это устанавливает ограничение памяти до 12 Мб

ini\_set("memory_limit","12M");
person Juan Pablo Califano    schedule 02.10.2008
comment
Примечание. Хотя это может помочь, это только откладывает проблему: как только появляется файл размером 15 МБ, проблема возвращается. (Если ваши файлы никогда не превысят определенный лимит, проблема может исчезнуть.) - person Piskvor left the building; 09.09.2010

для меня fopen() работает очень медленно с файлами размером более 1 МБ, file() намного быстрее.

Просто пытаясь прочитать 100 строк за раз и создать пакетную вставку, fopen() занимает 37 секунд, а file() занимает 4 секунды. Должен быть этот string->array шаг, встроенный в file()

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

person RightClick    schedule 04.08.2014

Вы пробовали file ()?

http://is2.php.net/manual/en/function.file.php

Или file_ get_contents ()

http://is2.php.net/manual/en/function.file-get-contents.php

person Ólafur Waage    schedule 02.10.2008
comment
Будьте осторожны с file_get_contents () для больших файлов. Хотя 6 мегабайт должно быть хорошо, потоковая передача намного лучше, поскольку она не считывает сначала весь файл в память. - person Dustin Graham; 10.04.2014