Come leggere un file di grandi dimensioni riga per riga?


470

Voglio leggere un file riga per riga, ma senza caricarlo completamente in memoria.

Il mio file è troppo grande per essere aperto in memoria e, se provo a farlo, ottengo sempre errori di memoria.

La dimensione del file è di 1 GB.


vedi la mia risposta a questo link
Sohail Ahmed,

7
Dovresti usare fgets()senza $lengthparametro.
Carlos,

26
Vorresti contrassegnare come risposta uno dei seguenti?
Kim impila il

Risposte:


685

È possibile utilizzare la fgets()funzione per leggere il file riga per riga:

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

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

3
Come funziona questo account per la too large to open in memoryparte?
Starx,

64
Non stai leggendo l'intero file in memoria. La memoria massima necessaria per eseguire ciò dipende dalla linea più lunga nell'input.
codaddict,

13
@Brandin - Moot - In quelle situazioni, la domanda posta, che è leggere un file LINE BY LINE, non ha un risultato ben definito.
ToolmakerSteve

3
@ToolmakerSteve Quindi definire cosa dovrebbe accadere. Se vuoi puoi semplicemente stampare il messaggio "Linea troppo lunga; rinunciare". e anche questo è un risultato ben definito.
Brandin,

2
Una riga può contenere un falso booleano? In tal caso, questo metodo si arresterebbe senza raggiungere la fine del file. L'esempio n. 1 su questo URL php.net/manual/en/function.fgets.php suggerisce che i budget a volte possono restituire booleani falsi anche se la fine del file non è stata ancora raggiunta. Nella sezione dei commenti su quella pagina le persone riportano che fgets () non restituisce sempre valori corretti, quindi è più sicuro usare feof come condizionale del ciclo.
Cjohansson,

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

8
Come ha detto @ Cuse70 nella sua risposta, questo porterà a un ciclo infinito se il file non esiste o non può essere aperto. Prova if($file)prima del ciclo while
FrancescoMM,

10
So che questo è vecchio, ma: l'uso di while (! Feof ($ file)) non è raccomandato. Dai un'occhiata qui.
Kevin Van Ryckegem,

A proposito: "Se non ci sono più dati da leggere nel puntatore del file, viene restituito FALSE." php.net/manual/en/function.fgets.php ... Per ogni evenienza
tutti i giorni

2
feof()non esiste più?
Ryan DuVal,

94

È possibile utilizzare una classe di interfaccia orientata agli oggetti per un file: 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
soluzione molto più pulita. grazie;) non hanno ancora utilizzato questa classe, ci sono le funzioni più interessanti da esplorare qui: php.net/manual/en/class.splfileobject.php
Lukas Liesis

6
Grazie. Sì, ad esempio puoi aggiungere questa riga prima mentre $ file-> setFlags (SplFileObject :: DROP_NEW_LINE); per far cadere nuove righe alla fine di una riga.
elshnkhll,

Per quanto posso vedere non c'è alcuna eof()funzione in SplFileObject?
Chud37,

3
Grazie! Inoltre, utilizzare rtrim($file->fgets())per rimuovere le nuove righe finali per ogni stringa di riga letta se non le si desidera.
racl101,


59

Se stai aprendo un file di grandi dimensioni, probabilmente vuoi usare Generators insieme a fgets () per evitare di caricare l'intero file in memoria:

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

Usalo in questo modo:

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

In questo modo è possibile elaborare singole righe di file all'interno di foreach ().

Nota: i generatori richiedono> = PHP 5.5


3
Questa dovrebbe essere una risposta accettata invece. È cento volte più veloce con i generatori.
Tachi,

1
E waaay più efficiente in termini di memoria.
Nino Škopac,

2
@ NinoŠkopac: puoi spiegare perché questa soluzione è più efficiente in termini di memoria? Ad esempio, rispetto SplFileObjectall'approccio.
k00ni,

30

Utilizzare le tecniche di buffering per leggere il file.

$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
questo merita più amore, poiché funzionerà con file di grandi dimensioni, anche con file che non hanno ritorni a
capo

Non sarei sorpreso se all'OP non interessassero davvero le linee reali e volessi semplicemente servire un download. In tal caso, questa risposta va bene (e cosa farebbe comunque la maggior parte dei programmatori PHP).
Álvaro González,

30

Esiste una file()funzione che restituisce una matrice delle linee contenute nel file.

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

28
Il file da un GB verrebbe letto in memoria e convertito in più di un array da GB ... buona fortuna.
FrancescoMM,

4
Questa non era la risposta alla domanda posta, ma risponde alla domanda più comune che molte persone hanno quando guardano qui, quindi è stato comunque utile, grazie.
pilavdzice,

2
file () è molto comodo per lavorare con file di piccole dimensioni. Soprattutto quando si desidera un array () come risultato finale.
functionvoid

questa è una cattiva idea con file più grandi dato che l'intero file viene letto su un array in una sola volta
Flash Thunder

Questo si rompe male su file di grandi dimensioni, quindi è esattamente il metodo che non funziona.
ftrotter


17

La risposta ovvia non era presente in tutte le risposte.
PHP ha a disposizione un parser delimitatore di streaming accurato creato proprio per quello scopo.

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

Va notato che questo codice restituirà solo le righe fino a quando si verifica la prima riga vuota. Devi testare $ line! == false nella condizione whilewhile (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false)
cebe

8

Fai attenzione alle cose 'while (! Feof ... fgets ()', i fgets possono ottenere un errore (returnfing false) e scorrere per sempre senza raggiungere la fine del file. il ciclo termina, controlla il feof; se non è vero, allora hai avuto un errore.


8

In questo modo riesco a gestire file di dimensioni molto grandi (testati con un massimo di 100 G). Ed è più veloce di 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);

come assicurate che il blocco 1024 * 1024 non si interrompa nel mezzo della linea?
user151496

1
@ user151496 easy !! contare ... 1.2.3.4
Omar El Don

@OmarElDon ​​cosa intendi?
Codex73,

7

Una delle soluzioni popolari a questa domanda avrà problemi con il nuovo carattere di linea. Può essere riparato abbastanza facilmente con un semplice str_replace.

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

6

SplFileObject è utile quando si tratta di gestire file di grandi dimensioni.

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

Funzione da leggere con ritorno di matrice

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
Ciò creerebbe un singolo array di più di un GB in memoria (buona fortuna con esso) diviso nemmeno in righe ma in blocchi di 4096 caratteri arbitrari. Perché mai vorresti farlo?
FrancescoMM,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.