Jak czytać duży plik linia po linii?

469

Chcę czytać plik linia po linii, ale bez pełnego ładowania go do pamięci.

Mój plik jest zbyt duży, aby otworzyć go w pamięci, a jeśli spróbuję to zrobić, zawsze mam problemy z pamięcią.

Rozmiar pliku to 1 GB.

adnan masood
źródło
zobacz moją odpowiedź pod tym linkiem
Sohail Ahmed
7
Powinieneś używać fgets()bez $lengthparametru.
Carlos,
26
Czy chcesz oznaczyć jako odpowiedź na którekolwiek z poniższych pytań?
Kim Stacks

Odpowiedzi:

684

Możesz użyć tej fgets()funkcji do odczytania pliku linia po linii:

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

    fclose($handle);
} else {
    // error opening the file.
} 
kodaddict
źródło
3
Jak działa to konto dla too large to open in memoryczęści?
Starx,
64
Nie czytasz całego pliku w pamięci. Maksymalna pamięć potrzebna do uruchomienia tego zależy od najdłuższej linii na wejściu.
codaddict
13
@Brandin - Moot - W takich sytuacjach zadane pytanie, które polega na odczytaniu pliku LINE BY LINE, nie ma dobrze określonego wyniku.
ToolmakerSteve
3
@ToolmakerSteve Następnie określ, co powinno się stać. Jeśli chcesz, możesz po prostu wydrukować komunikat „Zbyt długa linia; rezygnacja”. i jest to również dobrze określony wynik.
Brandin
2
Czy wiersz może zawierać wartość logiczną false? Jeśli tak, to ta metoda zatrzyma się bez osiągnięcia końca pliku. Przykład nr 1 na tym adresie php.net/manual/en/function.fgets.php sugeruje, że fgets mogą czasami zwracać wartość logiczną false, nawet jeśli nie osiągnięto jeszcze końca pliku. W sekcji komentarzy na tej stronie ludzie zgłaszają, że fgets () nie zawsze zwraca poprawne wartości, więc bezpieczniej jest używać feof jako pętli warunkowej.
cjohansson,
130
if ($file = fopen("file.txt", "r")) {
    while(!feof($file)) {
        $line = fgets($file);
        # do same stuff with the $line
    }
    fclose($file);
}
Syuaa SE
źródło
8
Jak powiedział @ Cuse70 w swojej odpowiedzi, doprowadzi to do nieskończonej pętli, jeśli plik nie istnieje lub nie można go otworzyć. Przetestuj if($file)przed pętlą while
FrancescoMM
10
Wiem, że to jest stare, ale: nie zaleca się używania while (! Feof ($ file)). Spójrz tutaj.
Kevin Van Ryckegem
BTW: „Jeśli we wskaźniku pliku nie ma więcej danych do odczytania, zwracana jest wartość FAŁSZ.” php.net/manual/en/function.fgets.php ... Na wszelki wypadek
everyman
2
feof()już nie istnieje?
Ryan DuVal
94

Możesz użyć obiektowej klasy interfejsu dla pliku - 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
źródło
3
znacznie czystsze rozwiązanie. dzięki;) jeszcze nie korzystałem z tej klasy, jest tu więcej ciekawych funkcji do eksploracji: php.net/manual/en/class.splfileobject.php
Lukas Liesis
6
Dzięki. Tak, na przykład możesz dodać tę linię wcześniej, podczas gdy $ file-> setFlags (SplFileObject :: DROP_NEW_LINE); aby usunąć nowe linie na końcu linii.
elshnkhll,
O ile widzę, eof()w SplFileObject nie ma żadnej funkcji?
Chud37
3
Dzięki! Użyj również, rtrim($file->fgets())aby usunąć końcowe znaki nowej linii dla każdego ciągu linii, który jest czytany, jeśli nie chcesz.
racl101
@ Chud37 tak jest: php.net/manual/en/splfileobject.eof.php
Nathan F.
59

Jeśli otwierasz duży plik, prawdopodobnie chcesz użyć Generatorów obok fgets (), aby uniknąć ładowania całego pliku do pamięci:

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

Użyj tego w ten sposób:

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

W ten sposób możesz przetwarzać pojedyncze linie plików w foreach ().

Uwaga: Generatory wymagają> = PHP 5.5

Nino Škopac
źródło
3
Zamiast tego powinna to być zaakceptowana odpowiedź. Jest setki razy szybszy dzięki generatorom.
Tachi,
1
I jeszcze bardziej wydajna pamięć.
Nino Škopac
2
@ NinoŠkopac: Czy możesz wyjaśnić, dlaczego to rozwiązanie jest bardziej wydajne pod względem pamięci? Na przykład w porównaniu z SplFileObjectpodejściem.
k00ni
30

Użyj technik buforowania, aby odczytać plik.

$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
źródło
2
zasługuje na więcej miłości, ponieważ będzie działać z dużymi plikami, nawet tymi, które nie mają
znaków
Nie zdziwiłbym się, gdyby OP tak naprawdę nie dbał o rzeczywiste linie i chciał tylko np. Podać plik do pobrania. W takim przypadku odpowiedź jest w porządku (i co zrobi większość koderów PHP).
Álvaro González
30

Tam jest file() funkcja, która zwraca tablicę wierszy zawartych w pliku.

foreach(file('myfile.txt') as $line) {
   echo $line. "\n";
}
NoImaginationGuy
źródło
28
Plik o rozmiarze jednego GB zostałby wczytany do pamięci i przekonwertowany na tablicę zawierającą więcej niż jeden GB ... powodzenia.
FrancescoMM
4
To nie była odpowiedź na zadane pytanie, ale odpowiada na bardziej powszechne pytanie, które ma wiele osób, patrząc tutaj, więc nadal było przydatne, dzięki.
pilavdzice 26.04.16
2
file () jest bardzo wygodny do pracy z małymi plikami. Zwłaszcza, gdy chcesz tablicę () jako wynik końcowy.
funkcja
to zły pomysł, gdy większe pliki są odczytywane do tablicy jednocześnie
Flash Thunder
To źle psuje się na dużych plikach, więc dokładnie ta metoda nie działa.
ftrotter
19
foreach (new SplFileObject(__FILE__) as $line) {
    echo $line;
}
Pytania o Kwolonel
źródło
Uwielbiam onelinerów
Nino Škopac
1
Onestatementers.
Pytania do kolonistów,
1
Wydajność pamięci w porównaniu do file().
Nobu,
17

Nie było oczywistej odpowiedzi we wszystkich odpowiedziach.
PHP ma schludny parser ograniczników strumieniowych dostępny specjalnie do tego celu.

$fp = fopen("/path/to/the/file", "r+");
while ($line = stream_get_line($fp, 1024 * 1024, "\n")) {
  echo $line;
}
fclose($fp);
Jan
źródło
Należy zauważyć, że ten kod zwróci tylko wiersze, dopóki nie pojawi się pierwszy pusty wiersz. Musisz przetestować na $ line! == false w trybie whilewhile (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false)
cebe
8

Uważaj na rzeczy „while (! Feof ... fgets ()”), fgets mogą otrzymać błąd (returnfing false) i zapętlić się na zawsze, nie osiągając końca pliku. kończy się pętla, sprawdź feof; jeśli nie jest prawdą, to wystąpił błąd.

Cuse70
źródło
8

Tak sobie radzę z bardzo dużym plikiem (testowanym do 100G). I jest szybszy niż 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
źródło
jak upewnić się, że blok 1024 * 1024 nie pęka w środku linii?
user151496,
1
@ user151496 easy !! liczyć ... 1.2.3.4
Omar El Don
@OmarElDon ​​co masz na myśli?
Kodeks73
7

Jednym z popularnych rozwiązań tego pytania będą problemy z nowym charakterem linii. Można to łatwo naprawić za pomocą prostego str_replace.

$handle = fopen("some_file.txt", "r");
if ($handle) {
    while (($line = fgets($handle)) !== false) {
        $line = str_replace("\n", "", $line);
    }
    fclose($handle);
}
Tegan Snyder
źródło
6

SplFileObject jest użyteczny, jeśli chodzi o obsługę dużych plików.

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
źródło
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
źródło
-8

Funkcja do odczytu ze znakiem powrotu tablicy

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
źródło
4
Stworzyłoby to jedną tablicę więcej niż jednego GB w pamięci (powodzenia) podzieloną nawet nie w wierszach, ale na dowolne fragmenty 4096 znaków. Dlaczego, u licha, chcesz to zrobić?
FrancescoMM