web-dev-qa-db-fra.com

Lecture de très gros fichiers dans PHP

fopen échoue lorsque j'essaie de lire un fichier de taille très modérée dans PHP. A 6 meg file le fait s'étouffer, bien que des fichiers plus petits autour de 100k conviennent parfaitement. J'ai lu qu'il est parfois nécessaire de recompiler PHP avec l'indicateur -D_FILE_OFFSET_BITS=64 afin de lire les fichiers de plus de 20 concerts ou quelque chose de ridicule, mais ne devrais-je pas avoir de problème avec un fichier de 6 meg? Finalement, nous voudrons lire des fichiers d’une taille d’environ 100 Mo, et il serait agréable de pouvoir les ouvrir puis de les lire ligne par ligne avec fgets, comme je suis capable de le faire avec des fichiers plus petits.

Quelles sont vos astuces/solutions pour lire et exécuter des opérations sur de très gros fichiers dans PHP?

Mise à jour: Voici un exemple de codeblock simple qui échoue sur mon fichier de 6 Mo - PHP ne semble pas générer d'erreur, il renvoie simplement false. Peut-être que je fais quelque chose d'extrêmement stupide?

$rawfile = "mediumfile.csv";

if($file = fopen($rawfile, "r")){  
  fclose($file);
} else {
  echo "fail!";
}

Une autre mise à jour: Merci à tous pour votre aide, cela s'est avéré être quelque chose d'incroyablement stupide - un problème d'autorisations. Mon petit fichier avait des autorisations de lecture inexplicablement, contrairement au fichier plus volumineux. Doh!

24
user5564

Etes-vous sûr que c'est fopen qui échoue et pas le paramètre de délai d'expiration de votre script? La valeur par défaut est généralement d’environ 30 secondes et si votre fichier prend plus de temps à lire, il risque de le déclencher.

Une autre chose à prendre en compte est peut-être la limite de mémoire de votre script - la lecture du fichier dans un tableau risque de trébucher, consultez votre journal des erreurs pour connaître les avertissements concernant la mémoire.

Si vous ne rencontrez aucun des problèmes ci-dessus, vous pouvez envisager d'utiliser fgets pour lire le fichier ligne par ligne et le traiter au fur et à mesure.

$handle = fopen("/tmp/uploadfile.txt", "r") or die("Couldn't get handle");
if ($handle) {
    while (!feof($handle)) {
        $buffer = fgets($handle, 4096);
        // Process buffer here..
    }
    fclose($handle);
}

Modifier

PHP ne semble pas générer d'erreur, il renvoie simplement false.

Le chemin d'accès à $rawfile est-il correct par rapport au lieu d'exécution du script? Essayez peut-être de définir ici un chemin absolu pour le nom de fichier.

43
ConroyP

J'ai fait 2 tests avec un fichier de 1,3 Go et un fichier de 9,5 Go.

1,3 Go

Utilisation de fopen()

Ce processus utilisait 15555 ms pour ses calculs.

Il a passé 169 ms en appels système.

Utilisation de file()

Ce processus a utilisé 6983 ms pour ses calculs.

Il a passé 4469 ms en appels système.

9,5 Go

Utilisation de fopen()

Ce processus a utilisé 113559 ms pour ses calculs.

Il a passé 2532 ms en appels système.

Utilisation de file()

Ce processus a utilisé 8221 ms pour ses calculs.

Il a passé 7998 ms en appels système.

Il semble que file() soit plus rapide.

7
Al-Punk

• La fonction fgets() convient jusqu'à ce que les fichiers texte atteignent 20 Mo et la vitesse d'analyse est considérablement réduite. 

• La fonction file_ get_contents() donne de bons résultats jusqu'à 40 Mo et les résultats acceptables jusqu'à 100 Mo, mais file_get_contents() charge l'intégralité du fichier en mémoire; il n'est donc pas scalabile. 

• La fonction file() est désastreuse avec les fichiers de texte volumineux, car elle crée un tableau contenant chaque ligne de texte. Ce tableau est donc stocké en mémoire et la mémoire utilisée est encore plus grande.
En fait, un fichier de 200 Mo que je ne parvenais qu'à analyser avec memory_limit défini à 2 Go, ce qui était inapproprié pour les fichiers de 1 Go ou plus que je voulais analyser. 

Lorsque vous devez analyser des fichiers de plus de 1 Go et que le temps d'analyse dépasse 15 secondes et que vous souhaitez éviter de charger l'intégralité du fichier en mémoire, vous devez trouver un autre moyen. 

Ma solution consistait à analyser les données en petits morceaux arbitraires. Le code est: 

$filesize = get_file_size($file);
$fp = @fopen($file, "r");

// if handle $fp to file was created, go ahead
if ($fp) {
   while(!feof($fp)){
      $chunk_size = (1<<24); // 16MB arbitrary
      $position = 0;

      // move pointer to $position in file
      fseek($fp, $position);

      // take a slice of $chunk_size bytes
      $chunk = fread($fp,$chunk_size);

      // searching the end of last full text line
      $last_lf_pos = strrpos($chunk, "\n");
      $chunk = NULL;

      // $buffer will contain full lines of text
      // starting from $position to $last_lf_pos
      $buffer = fread($fp,$last_lf_pos);

      ////////////////////////////////////////////////////
      //// ... DO SOMETHING WITH THIS BUFFER HERE ... ////
      ////////////////////////////////////////////////////

      // Move $position
      $position += $last_lf_pos;

      // if remaining is less than $chunk_size, make $chunk_size equal remaining
      if(($position+$chunk_size) > $filesize) $chunk_size = $filesize-$position;
      $buffer = NULL;
   }
   fclose($fp);
}

La mémoire utilisée est uniquement le $chunk_size et la vitesse est légèrement inférieure à celle obtenue avec file_ get_contents(). Je pense que PHP Group devrait utiliser mon approche afin d'optimiser ses fonctions d'analyse syntaxique. 

*) Trouver la fonction get_file_size()ici . 

1
Tinel Barb

J'ai utilisé fopen pour ouvrir des fichiers vidéo en streaming, en utilisant un script php comme serveur de streaming vidéo, et je n'ai eu aucun problème avec des fichiers de plus de 50/60 Mo.

1
Enrico Murru

pour moi, fopen() a été très lent avec des fichiers de plus de 1 Mo. file() est beaucoup plus rapide.

En essayant de lire les lignes 100 à la fois et de créer des insertions par lots, fopen() prend 37 secondes, alors que file() prend 4 secondes. Doit être cette étape string->array intégrée à file()

J'essaierais toutes les options de gestion de fichiers pour voir laquelle fonctionnera le mieux avec votre application.

0
RightClick

Vous pouvez bien essayer d’utiliser la fonction readfile si vous voulez simplement sortir le fichier.

Si ce n'est pas le cas - vous devriez peut-être réfléchir à la conception de l'application, pourquoi voulez-vous ouvrir des fichiers aussi volumineux sur des requêtes Web?

0
Fionn

Si le problème est causé par le dépassement de la limite de mémoire, vous pouvez essayer de lui attribuer une valeur plus élevée (cela peut fonctionner ou non en fonction de la configuration de php).

cela définit la limite de mémoire à 12 Mo

ini\_set("memory_limit","12M");
0