Wie lese ich eine große Datei Zeile für Zeile?


469

Ich möchte eine Datei Zeile für Zeile lesen, ohne sie jedoch vollständig in den Speicher zu laden.

Meine Datei ist zu groß, um im Speicher geöffnet zu werden. Wenn ich dies versuche, werden immer Speicherfehler angezeigt.

Die Dateigröße beträgt 1 GB.


siehe meine Antwort unter diesem Link
Sohail Ahmed

7
Sie sollten fgets()ohne $lengthParameter verwenden.
Carlos

26
Möchten Sie eine der folgenden Antworten als Antwort markieren?
Kim Stacks

Antworten:


684

Mit der fgets()Funktion können Sie die Datei Zeile für Zeile lesen:

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

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

3
Wie erklärt dies das too large to open in memoryTeil?
Starx

64
Sie lesen nicht die gesamte Datei im Speicher. Der maximale Speicher, der zum Ausführen benötigt wird, hängt von der längsten Zeile in der Eingabe ab.
Codaddict

13
@Brandin - Moot - In diesen Situationen hat die gestellte Frage, eine Datei LINE BY LINE zu lesen, kein genau definiertes Ergebnis.
ToolmakerSteve

3
@ToolmakerSteve Definieren Sie dann, was passieren soll. Wenn Sie möchten, können Sie einfach die Nachricht "Zeile zu lang; Aufgeben" ausdrucken. und das ist auch ein genau definiertes Ergebnis.
Brandin

2
Kann eine Zeile einen booleschen Wert false enthalten? In diesem Fall wird diese Methode beendet, ohne das Dateiende zu erreichen. Das Beispiel Nr. 1 unter dieser URL php.net/manual/en/function.fgets.php legt nahe, dass fgets manchmal boolean false zurückgeben können, obwohl das Dateiende noch nicht erreicht wurde. Im Kommentarbereich auf dieser Seite wird berichtet, dass fgets () nicht immer die richtigen Werte zurückgibt. Daher ist es sicherer, feof als Schleifenbedingung zu verwenden.
Cjohansson

130
if ($file = fopen("file.txt", "r")) {
    while(!feof($file)) {
        $line = fgets($file);
        # do same stuff with the $line
    }
    fclose($file);
}

8
Wie @ Cuse70 in seiner Antwort sagte, führt dies zu einer Endlosschleife, wenn die Datei nicht existiert oder nicht geöffnet werden kann. Test für if($file)vor der while-Schleife
FrancescoMM

10
Ich weiß, dass dies alt ist, aber: Die Verwendung von while (! Feof ($ file)) wird nicht empfohlen. Schauen Sie hier.
Kevin Van Ryckegem

Übrigens: "Wenn im Dateizeiger keine Daten mehr zu lesen sind, wird FALSE zurückgegeben." php.net/manual/en/function.fgets.php ... Nur für den Fall
Jedermann

2
feof()existiert nicht mehr?
Ryan DuVal

94

Sie können eine objektorientierte Schnittstellenklasse für eine Datei verwenden - 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
viel sauberere Lösung. danke;) habe diese Klasse noch nicht benutzt, es gibt hier weitere interessante Funktionen zu entdecken
Lukas Liesis

6
Vielen Dank. Ja, Sie können diese Zeile beispielsweise vorher hinzufügen, während $ file-> setFlags (SplFileObject :: DROP_NEW_LINE); um Zeilenumbrüche am Ende einer Zeile zu löschen.
Elshnkhll

Soweit ich sehen kann, gibt es eof()in SplFileObject keine Funktion?
Chud37

3
Vielen Dank! Verwenden Sie rtrim($file->fgets())diese Option auch, um nachfolgende Zeilenumbrüche für jede gelesene Zeilenzeichenfolge zu entfernen, wenn Sie sie nicht möchten.
racl101


59

Wenn Sie eine große Datei öffnen, möchten Sie wahrscheinlich Generatoren neben fgets () verwenden, um zu vermeiden, dass die gesamte Datei in den Speicher geladen wird:

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

Verwenden Sie es so:

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

Auf diese Weise können Sie einzelne Dateizeilen innerhalb von foreach () verarbeiten.

Hinweis: Generatoren benötigen> = PHP 5.5


3
Dies sollte stattdessen eine akzeptierte Antwort sein. Mit Generatoren ist es hundertmal schneller.
Tachi

1
Und waaay speichereffizienter.
Nino Škopac

2
@ NinoŠkopac: Können Sie erklären, warum diese Lösung speichereffizienter ist? Zum Beispiel im Vergleich zum SplFileObjectAnsatz.
k00ni

30

Verwenden Sie Puffertechniken, um die Datei zu lesen.

$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
Dies verdient mehr Liebe, da es mit riesigen Dateien funktioniert, auch mit Dateien, die keine Wagenrückläufe oder übermäßig lange Zeilen haben ...
Jimmery

Es würde mich nicht wundern, wenn sich das OP nicht wirklich um die tatsächlichen Leitungen kümmern würde und nur z. B. einen Download bereitstellen wollte. In diesem Fall ist diese Antwort in Ordnung (und was die meisten PHP-Codierer sowieso tun würden).
Álvaro González

30

Es gibt eine file()Funktion, die ein Array der in der Datei enthaltenen Zeilen zurückgibt.

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

28
Die 1-GB-Datei würde alle in den Speicher eingelesen und in ein Array mit mehr als einem GB konvertiert ... viel Glück.
FrancescoMM

4
Dies war nicht die Antwort auf die gestellte Frage, aber es beantwortet die häufigere Frage, die viele Leute haben, wenn sie hier suchen. Es war also immer noch nützlich, danke.
Pilavdzice

2
file () ist sehr praktisch für die Arbeit mit kleinen Dateien. Besonders wenn Sie ein Array () als Endergebnis wollen.
Funktionvoid

Dies ist eine schlechte Idee bei größeren Dateien, da die gesamte Datei auf einmal in ein Array gelesen wird
Flash Thunder

Dies funktioniert bei großen Dateien schlecht, daher funktioniert genau diese Methode nicht.
ftrotter


17

Die offensichtliche Antwort war nicht in allen Antworten vorhanden.
PHP verfügt über einen ordentlichen Streaming-Trennzeichen-Parser, der genau für diesen Zweck entwickelt wurde.

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

Es ist zu beachten, dass dieser Code nur Zeilen zurückgibt, bis die erste leere Zeile auftritt. Sie müssen in der while-Bedingung auf $ line! == false testenwhile (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false)
cebe

8

Seien Sie vorsichtig mit dem 'while (! Feof ... fgets ()' - Zeug, fgets können einen Fehler bekommen (returnfing false) und für immer eine Schleife ausführen, ohne das Dateiende zu erreichen. Codaddict war der Richtigkeit am nächsten, aber wenn Ihr 'while fgets' Schleife endet, überprüfen Sie feof; wenn nicht wahr, dann hatten Sie einen Fehler.


8

So verwalte ich mit sehr großen Dateien (getestet mit bis zu 100G). Und es ist schneller als 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);

Wie stellen Sie sicher, dass der 1024 * 1024-Block nicht in der Mitte der Zeile unterbrochen wird?
user151496

1
@ user151496 einfach !! count ... 1.2.3.4
Omar El Don

@OmarElDon ​​was meinst du?
Codex73

7

Eine der beliebtesten Lösungen für diese Frage wird Probleme mit dem neuen Linienzeichen haben. Es kann ziemlich einfach mit einem einfachen behoben werden str_replace.

$handle = fopen("some_file.txt", "r");
if ($handle) {
    while (($line = fgets($handle)) !== false) {
        $line = str_replace("\n", "", $line);
    }
    fclose($handle);
}

6

SplFileObject ist nützlich, wenn es um den Umgang mit großen Dateien geht.

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

Funktion zum Lesen mit Array-Rückgabe

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
Dies würde ein einzelnes Array mit mehr als einem GB Speicher erzeugen (viel Glück damit), das nicht einmal in Zeilen, sondern in willkürliche 4096-Zeichen-Blöcke unterteilt ist. Warum um alles in der Welt willst du das tun?
FrancescoMM
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.