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.

Adnan Masood
quelle
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.
} 
Codaddict
quelle
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);
}
Syuaa SE
quelle
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;
elshnkhll
quelle
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
@ Chud37 Ja, es gibt: php.net/manual/en/splfileobject.eof.php
Nathan F.
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

Nino Škopac
quelle
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);
    ///
}
Starx
quelle
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";
}
NoImaginationGuy
quelle
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
19
foreach (new SplFileObject(__FILE__) as $line) {
    echo $line;
}
Quolonel Fragen
quelle
Ich muss die Oneliner lieben
Nino Škopac
1
Onestatementers.
Quolonel Fragen
1
Speichereffizient im Vergleich zu file().
Nobu
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);
John
quelle
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.

Cuse70
quelle
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);
Metodi Darzev
quelle
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);
}
Tegan Snyder
quelle
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;
}
xanadev
quelle
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
quelle
-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;
}
sixvel.com
quelle
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