Comment lire un gros fichier ligne par ligne?


470

Je veux lire un fichier ligne par ligne, mais sans le charger complètement en mémoire.

Mon fichier est trop volumineux pour être ouvert en mémoire, et si j'essaie de le faire, je sors toujours des erreurs de mémoire.

La taille du fichier est de 1 Go.


voir ma réponse sur ce lien
Sohail Ahmed

7
Vous devez utiliser fgets()sans $lengthparamètre.
Carlos

26
Souhaitez-vous marquer comme réponse l'un des éléments suivants?
Kim Stacks

Réponses:


685

Vous pouvez utiliser la fgets()fonction pour lire le fichier ligne par ligne:

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

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

3
Comment cela explique-t-il la too large to open in memorypièce?
Starx

64
Vous ne lisez pas l'intégralité du fichier en mémoire. La mémoire maximale nécessaire pour exécuter cela dépend de la ligne la plus longue en entrée.
codaddict

13
@Brandin - Moot - Dans ces situations, la question posée, qui est de lire un fichier LIGNE PAR LIGNE, n'a pas de résultat bien défini.
ToolmakerSteve

3
@ToolmakerSteve Définissez ensuite ce qui doit se produire. Si vous le souhaitez, vous pouvez simplement imprimer le message "Ligne trop longue; abandonner". et c'est aussi un résultat bien défini.
Brandin

2
Une ligne peut-elle contenir un faux booléen? Si c'est le cas, cette méthode s'arrêterait sans atteindre la fin du fichier. L'exemple n ° 1 sur cette URL php.net/manual/en/function.fgets.php suggère que les fgets peuvent parfois renvoyer un booléen faux même si la fin du fichier n'a pas encore été atteinte. Dans la section des commentaires de cette page, les utilisateurs signalent que fgets () ne renvoie pas toujours des valeurs correctes, il est donc plus sûr d'utiliser feof comme conditionnelle de la boucle.
cjohansson

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

8
Comme @ Cuse70 l'a dit dans sa réponse, cela conduira à une boucle infinie si le fichier n'existe pas ou ne peut pas être ouvert. Testez if($file)avant la boucle while
FrancescoMM

10
Je sais que c'est vieux, mais: l'utilisation de while (! Feof ($ file)) n'est pas recommandée. Jetez un oeil ici.
Kevin Van Ryckegem

BTW: "S'il n'y a plus de données à lire dans le pointeur de fichier, FALSE est renvoyé." php.net/manual/en/function.fgets.php ... Juste au cas où
Everyman

2
feof()n'existe plus?
Ryan DuVal

94

Vous pouvez utiliser une classe d'interface orientée objet pour un fichier - 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
solution beaucoup plus propre. merci;) n'ont pas encore utilisé cette classe, il existe d'autres fonctions intéressantes à explorer: php.net/manual/en/class.splfileobject.php
Lukas Liesis

6
Merci. Oui, par exemple, vous pouvez ajouter cette ligne avant tandis que $ file-> setFlags (SplFileObject :: DROP_NEW_LINE); afin de supprimer les sauts de ligne à la fin d'une ligne.
elshnkhll

Pour autant que je puisse voir, il n'y a pas de eof()fonction dans SplFileObject?
Chud37

3
Merci! Utilisez également rtrim($file->fgets())pour supprimer les sauts de ligne de fin pour chaque chaîne de ligne lue si vous ne les souhaitez pas.
racl101


59

Si vous ouvrez un gros fichier, vous voudrez probablement utiliser Generators à côté de fgets () pour éviter de charger tout le fichier en mémoire:

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

Utilisez-le comme ceci:

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

De cette façon, vous pouvez traiter des lignes de fichiers individuelles à l'intérieur de foreach ().

Remarque: les générateurs nécessitent> = PHP 5.5


3
Cela devrait plutôt être une réponse acceptée. C'est cent fois plus rapide avec des générateurs.
Tachi

1
Et waaay plus efficace en mémoire.
Nino Škopac

2
@ NinoŠkopac: Pouvez-vous expliquer pourquoi cette solution est plus économe en mémoire? Par exemple, par rapport à l' SplFileObjectapproche.
k00ni

30

Utilisez des techniques de mise en mémoire tampon pour lire le fichier.

$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
cela mérite plus d'amour, car cela fonctionnera avec des fichiers énormes, même des fichiers qui n'ont pas de retour chariot ou des lignes extrêmement longues ...
Jimmery

Je ne serais pas surpris si l'OP ne se souciait pas vraiment des lignes réelles et voulait juste par exemple servir un téléchargement. Dans ce cas, cette réponse est très bien (et ce que la plupart des codeurs PHP feraient de toute façon).
Álvaro González

30

Il existe une file()fonction qui renvoie un tableau des lignes contenues dans le fichier.

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

28
Le fichier d'un Go serait tout lu en mémoire et converti en plus d'un tableau de Go ... bonne chance.
FrancescoMM

4
Ce n'était pas la réponse à la question posée, mais cela répond à la question la plus courante que beaucoup de gens se posent en regardant ici, donc c'était toujours utile, merci.
pilavdzice

2
file () est très pratique pour travailler avec de petits fichiers. Surtout quand vous voulez un tableau () comme résultat final.
Fonctionvide

c'est une mauvaise idée avec des fichiers plus gros car tout le fichier est lu dans un tableau à la fois
Flash Thunder

Cela casse mal sur les gros fichiers, c'est donc exactement la méthode qui ne fonctionne pas.
ftrotter


17

La réponse évidente n'était pas là dans toutes les réponses.
PHP a un analyseur délimiteur de streaming soigné disponible spécialement conçu à cet effet.

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

Il convient de noter que ce code ne renverra que des lignes jusqu'à ce que la première ligne vide se produise. Vous devez tester $ line! == false dans la condition while (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false)
while

8

Soyez prudent avec le truc 'while (! Feof ... fgets ()', fgets peut obtenir une erreur (returnfing false) et boucler indéfiniment sans atteindre la fin du fichier. Codaddict était le plus proche d'être correct mais quand votre 'while fgets' boucle se termine, vérifiez feof; si ce n'est pas vrai, alors vous avez eu une erreur.


8

C'est comme ça que je gère avec un très gros fichier (testé jusqu'à 100G). Et c'est plus rapide que 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);

comment vous assurez-vous que le bloc 1024 * 1024 ne se casse pas au milieu de la ligne?
user151496

1
@ user151496 facile !! compte ... 1.2.3.4
Omar El Don

@OmarElDon ​​que voulez-vous dire?
Codex73

7

L'une des solutions populaires à cette question aura des problèmes avec le nouveau caractère de ligne. Il peut être fixé assez facilement avec un simple str_replace.

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

6

SplFileObject est utile lorsqu'il s'agit de traiter des fichiers volumineux.

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

Fonction à lire avec retour de tableau

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
Cela créerait un tableau unique de plus d'un Go en mémoire (bonne chance avec lui) divisé même pas en lignes mais en morceaux arbitraires de 4096 caractères. Pourquoi diable voudriez-vous faire ça?
FrancescoMM
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.