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.


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

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

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;

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


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


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

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

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


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

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.


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

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

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

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

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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.