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.