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

470

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

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

Размер файла составляет 1 ГБ.

Аднан Масуд
источник
см. мой ответ по этой ссылке
Сохаил Ахмед
7
Вы должны использовать fgets()без $lengthпараметра.
Карлос
26
Хотели бы вы отметить как ответ на любой из следующих?
Ким Стеки

Ответы:

685

Вы можете использовать fgets()функцию для чтения файла построчно:

$handle = fopen("inputfile.txt", "r");
if ($handle) {
    while (($line = fgets($handle)) !== false) {
        // process the line read.
    }

    fclose($handle);
} else {
    // error opening the file.
} 
codaddict
источник
3
Как этот счет для too large to open in memoryчасти?
Starx
64
Вы не читаете весь файл в памяти. Максимальный объем памяти, необходимый для запуска, зависит от самой длинной строки на входе.
codaddict
13
@Brandin - Moot - В этих ситуациях заданный вопрос, который заключается в чтении файла LINE BY LINE, не имеет четко определенного результата.
ToolmakerSteve
3
@ToolmakerSteve Тогда определите, что должно произойти. Если вы хотите, вы можете просто напечатать сообщение «Строка слишком длинная; сдача». и это тоже четко определенный результат.
Брандин
2
Может ли строка содержать логическое значение false? Если это так, то этот метод остановится, не достигнув конца файла. Пример №1 на этом URL php.net/manual/en/function.fgets.php предполагает, что fgets иногда может возвращать логическое значение false, даже если конец файла еще не достигнут. В разделе комментариев на этой странице люди сообщают, что fgets () не всегда возвращает правильные значения, поэтому безопаснее использовать feof в качестве условного цикла.
cjohansson
131
if ($file = fopen("file.txt", "r")) {
    while(!feof($file)) {
        $line = fgets($file);
        # do same stuff with the $line
    }
    fclose($file);
}
Сюаа С.Е.
источник
8
Как сказал @ Cuse70 в своем ответе, это приведет к бесконечному циклу, если файл не существует или не может быть открыт. Тест if($file)до цикла while
FrancescoMM
10
Я знаю, что это старый, но: использование while (! Feof ($ file)) не рекомендуется. Посмотрите здесь.
Кевин Ван Рикегем,
Кстати: «Если в указателе файла больше нет данных для чтения, то возвращается FALSE». php.net/manual/en/function.fgets.php ... На всякий случай
каждый человек
2
feof()больше не существует?
Райан Дюваль
94

Вы можете использовать класс объектно-ориентированного интерфейса для файла - SplFileObject http://php.net/manual/en/splfileobject.fgets.php (PHP 5> = 5.1.0)

<?php

$file = new SplFileObject("file.txt");

// Loop until we reach the end of the file.
while (!$file->eof()) {
    // Echo one line from the file.
    echo $file->fgets();
}

// Unset the file to call __destruct(), closing the file handle.
$file = null;
elshnkhll
источник
3
намного более чистое решение. спасибо;) еще не использовал этот класс, здесь есть более интересные функции для изучения: php.net/manual/en/class.splfileobject.php
Lukas Liesis
6
Спасибо. Да, например, вы можете добавить эту строку раньше, пока $ file-> setFlags (SplFileObject :: DROP_NEW_LINE); для того, чтобы сбросить символы новой строки в конце строки.
elshnkhll
Насколько я вижу, нет eof()функции в SplFileObject?
Chud37
3
Спасибо! Кроме того, используйте rtrim($file->fgets())для удаления завершающих строк новой строки для каждой строки, которая читается, если вы не хотите их.
racl101
@ Chud37 да, есть: php.net/manual/en/splfileobject.eof.php
Натан Ф.
59

Если вы открываете большой файл, вы, вероятно, захотите использовать Generators вместе с fgets (), чтобы избежать загрузки всего файла в память:

/**
 * @return Generator
 */
$fileData = function() {
    $file = fopen(__DIR__ . '/file.txt', 'r');

    if (!$file)
        die('file does not exist or cannot be opened');

    while (($line = fgets($file)) !== false) {
        yield $line;
    }

    fclose($file);
};

Используйте это так:

foreach ($fileData() as $line) {
    // $line contains current line
}

Таким образом, вы можете обрабатывать отдельные строки файла внутри foreach ().

Примечание: генераторы требуют> = PHP 5.5

Нино Шкопац
источник
3
Это должен быть принятый ответ. Это в сто раз быстрее с генераторами.
Тачи
1
И ваааааааа более эффективная память.
Нино Шкопац
2
@ NinoŠkopac: Можете ли вы объяснить, почему это решение более эффективно использует память? Например, по сравнению с SplFileObjectподходом.
k00ni
30

Используйте методы буферизации для чтения файла.

$filename = "test.txt";
$source_file = fopen( $filename, "r" ) or die("Couldn't open $filename");
while (!feof($source_file)) {
    $buffer = fread($source_file, 4096);  // use a buffer of 4KB
    $buffer = str_replace($old,$new,$buffer);
    ///
}
Starx
источник
2
это заслуживает большей любви, так как будет работать с огромными файлами, даже с файлами, у которых нет возврата каретки или слишком длинных строк ...
Джиммери
Я не был бы удивлен, если бы OP действительно не заботился о реальных строках и просто хотел, например, подать загрузку. В этом случае этот ответ очень хорош (и то, что в любом случае сделает большинство PHP-кодеров).
Альваро Гонсалес
30

Есть file()функция, которая возвращает массив строк, содержащихся в файле.

foreach(file('myfile.txt') as $line) {
   echo $line. "\n";
}
NoImaginationGuy
источник
28
Все файлы объемом один ГБ будут считаны в память и преобразованы в массив размером более одного ГБ ... удачи.
FrancescoMM
4
Это был не ответ на заданный вопрос, но он отвечает на более распространенный вопрос, который возникает у многих людей при поиске здесь, так что это все равно было полезно, спасибо.
Pilavdzice
2
file () очень удобен для работы с небольшими файлами. Особенно, когда вы хотите массив () в качестве конечного результата.
functionvoid
это плохая идея с большими файлами, так как весь файл читается в массив сразу
Flash Thunder
Это плохо работает с большими файлами, поэтому именно этот метод не работает.
ftrotter
19
foreach (new SplFileObject(__FILE__) as $line) {
    echo $line;
}
Quolonel Вопросы
источник
Должен любить oneliners
Нино Шкопац
1
Onestatementers.
Quolonel Вопросы
1
Память эффективна по сравнению с file().
Нобу
17

Очевидного ответа не было во всех ответах.
В PHP имеется удобный анализатор потокового разделителя, созданный именно для этой цели.

$fp = fopen("/path/to/the/file", "r+");
while ($line = stream_get_line($fp, 1024 * 1024, "\n")) {
  echo $line;
}
fclose($fp);
Джон
источник
Следует отметить, что этот код будет возвращать только строки до появления первой пустой строки. Вам нужно проверить на $ line! == false в состоянии whilewhile (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false)
cebe
8

Будьте осторожны с вещами 'while (! Feof ... fgets ()'), fgets может получить ошибку (returnfing false) и зацикливаться вечно, не доходя до конца файла. Codaddict был наиболее близок к правильному, но когда ваш 'while fgets' цикл заканчивается, проверьте feof, если не соответствует истине, то произошла ошибка.

Cuse70
источник
8

Вот как я справляюсь с очень большими файлами (проверено до 100G). И это быстрее, чем fgets ()

$block =1024*1024;//1MB or counld be any higher than HDD block_size*2
if ($fh = fopen("file.txt", "r")) { 
    $left='';
    while (!feof($fh)) {// read the file
       $temp = fread($fh, $block);  
       $fgetslines = explode("\n",$temp);
       $fgetslines[0]=$left.$fgetslines[0];
       if(!feof($fh) )$left = array_pop($lines);           
       foreach ($fgetslines as $k => $line) {
           //do smth with $line
        }
     }
}
fclose($fh);
Методи Дарзев
источник
Как вы гарантируете, что блок 1024 * 1024 не сломается в середине строки?
user151496
1
@ user151496 легко !! считай ... 1.2.3.4
Омар Эль Дон
@ OmarElDon ​​что ты имеешь ввиду?
Codex73
7

Одно из популярных решений этого вопроса будет иметь проблемы с символом новой строки. Это можно легко исправить с помощью простого str_replace.

$handle = fopen("some_file.txt", "r");
if ($handle) {
    while (($line = fgets($handle)) !== false) {
        $line = str_replace("\n", "", $line);
    }
    fclose($handle);
}
Теган Снайдер
источник
6

SplFileObject полезен, когда речь идет о работе с большими файлами.

function parse_file($filename)
{
    try {
        $file = new SplFileObject($filename);
    } catch (LogicException $exception) {
        die('SplFileObject : '.$exception->getMessage());
    }
    while ($file->valid()) {
        $line = $file->fgets();
        //do something with $line
    }

    //don't forget to free the file handle.
    $file = null;
}
xanadev
источник
1
<?php
echo '<meta charset="utf-8">';

$k= 1;
$f= 1;
$fp = fopen("texttranslate.txt", "r");
while(!feof($fp)) {
    $contents = '';
    for($i=1;$i<=1500;$i++){
        echo $k.' -- '. fgets($fp) .'<br>';$k++;
        $contents .= fgets($fp);
    }
    echo '<hr>';
    file_put_contents('Split/new_file_'.$f.'.txt', $contents);$f++;
}
?>
Nguyễn Văn Cường
источник
-8

Функция для чтения с возвратом массива

function read_file($filename = ''){
    $buffer = array();
    $source_file = fopen( $filename, "r" ) or die("Couldn't open $filename");
    while (!feof($source_file)) {
        $buffer[] = fread($source_file, 4096);  // use a buffer of 4KB
    }
    return $buffer;
}
sixvel.com
источник
4
Это позволило бы создать один массив объемом более одного ГБ в памяти (удачи с ним), разделенный даже не на строки, а на произвольные 4096 символов. С какой стати вы хотите это сделать?
FrancescoMM