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.

adnan masood
la source
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.
} 
codaddict
la source
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);
}
Syuaa SE
la source
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;
elshnkhll
la source
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
@ Chud37 oui il y a: php.net/manual/en/splfileobject.eof.php
Nathan F.
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

Nino Škopac
la source
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);
    ///
}
Starx
la source
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";
}
NoImaginationGuy
la source
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
19
foreach (new SplFileObject(__FILE__) as $line) {
    echo $line;
}
Questions sur Quolonel
la source
Faut aimer les oneliners
Nino Škopac
1
Onestatementers.
Quolonel Questions
1
Mémoire efficace par rapport à file().
Nobu
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);
John
la source
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.

Cuse70
la source
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);
Metodi Darzev
la source
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);
}
Tegan Snyder
la source
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;
}
xanadev
la source
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
la source
-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;
}
sixvel.com
la source
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