Як читати великий файл за рядком?


469

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

Мій файл занадто великий, щоб відкрити в пам'яті, і якщо намагаюся це зробити, я завжди виходжу з помилок пам'яті.

Розмір файлу - 1 Гб.


дивіться мою відповідь за цим посиланням
Sohail Ahmed

7
Ви повинні використовувати fgets()без $lengthпараметра.
Карлос

26
Чи хотіли б ви позначити як відповідь на що-небудь із наступного?
Кім Стек

Відповіді:


684

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

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

    fclose($handle);
} else {
    // error opening the file.
} 

3
Як цей рахунок складається з too large to open in memoryчастини?
Starx

64
Ви не читаєте весь файл в пам'яті. Максимальна пам'ять, необхідна для цього, залежить від найдовшого рядка на вході.
кодифікаційний вирок

13
@Brandin - Moot - У тих випадках задане запитання, яке полягає у читанні файлу LINE BY LINE, не має чітко визначеного результату.
ToolmakerSteve

3
@ToolmakerSteve Потім визначте, що має статися. Якщо ви хочете, ви можете просто надрукувати повідомлення "Рядок занадто довгий; здавайся". і це теж чітко визначений результат.
Брандін

2
Чи може рядок містити булеву помилку? Якщо так, то цей спосіб зупиниться, не дійшовши до кінця файлу. Приклад №1 у цій URL-адресі php.net/manual/en/function.fgets.php дозволяє припустити, що індекси іноді можуть повернути булеву помилку, хоча кінець файлу ще не досягнуто. У розділі коментарів на цій сторінці люди повідомляють, що fgets () не завжди повертає правильні значення, тому безпечніше використовувати feof як цикл умовного.
cjohansson

130
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)) не рекомендується. Подивіться тут.
Кевін Ван Ріккегем

BTW: "Якщо в покажчику файлу більше даних для читання, FALSE повертається." php.net/manual/en/function.fgets.php ... Про всяк випадок
кожний

2
feof()більше не існує?
Ryan DuVal

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;

3
набагато чистіший розчин. спасибі;) ще не використовували цей клас, тут є цікавіші функції для вивчення: php.net/manual/en/class.splfileobject.php
Лукас Ліїс

6
Дякую. Так, наприклад, ви можете додати цей рядок раніше, поки $ file-> setFlags (SplFileObject :: DROP_NEW_LINE); щоб випустити нові рядки в кінці рядка.
elshnkhll

Наскільки я можу бачити, що eof()в SplFileObject немає жодної функції?
Chud37

3
Дякую! Крім того, використовуйте rtrim($file->fgets())для зняття нових рядків для кожного рядка рядка, який читається, якщо ви не хочете їх.
racl101


59

Якщо ви відкриваєте великий файл, ви, ймовірно, хочете використовувати Генератори поряд з 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
І waaay ефективніше пам’яті.
Ніно Шкопач

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);
    ///
}

2
це заслуговує більшої любові, оскільки воно буде працювати з величезними файлами, навіть файлами, які не мають повернення каретки або дуже довгих рядків ...
Jimmery

Я не був би здивований, якби ОП не дуже піклувався про фактичні лінії та просто хотів, наприклад, подати завантаження. У такому випадку ця відповідь просто чудова (і що б у більшості випадків зробили PHP-кодери).
Альваро Гонсалес

30

Існує file()функція, яка повертає масив рядків, що містяться у файлі.

foreach(file('myfile.txt') as $line) {
   echo $line. "\n";
}

28
Файл одного ГБ буде прочитаний у пам'яті та перетворений у масив більше ГБ ... удачі.
FrancescoMM

4
Це не було відповіддю на поставлене запитання, але це відповідь на більш поширене питання, яке виникає у багатьох людей, коли дивляться сюди, тож все-таки було корисно, дякую.
pilavdzice

2
file () дуже зручний для роботи з невеликими файлами. Особливо, коли ви хочете масив () як кінцевий результат.
functionvoid

це погана ідея з більшими файлами, оскільки весь файл читається в масив одразу
Flash Thunder

Це сильно розбивається на великих файлах, тому саме такий метод не працює.
ftrotter


17

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

$fp = fopen("/path/to/the/file", "r+");
while ($line = stream_get_line($fp, 1024 * 1024, "\n")) {
  echo $line;
}
fclose($fp);

Слід зазначити, що цей код поверне лише рядки до появи першого порожнього рядка. Вам потрібно перевірити на $ line! == false в той час, коли станwhile (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false)
cebe

8

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


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 не зламається посередині рядка?
користувач151496

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;
}

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++;
}
?>

-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;
}

4
Це створило б єдиний масив з більш ніж одного ГБ пам’яті (удача з ним), розділеного навіть не на рядки, а на довільні шматки 4096 символів. Чому б на землі ти хотів це зробити?
FrancescoMM
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.