11
votes

Manipuler une chaîne de 30 millions de caractères

Je télécharge un fichier CSV d'un autre serveur en tant que flux de données d'un fournisseur.

J'utilise CURL pour obtenir le contenu du fichier et enregistrer cela dans une variable appelée $ contenu .

Je peux arriver à cette partie juste bien, mais j'ai essayé d'exploser par \ r et \ n pour obtenir une gamme de lignes mais elle échoue avec un "hors de Mémoire 'Erreur.

i Echo Strlen ($ contenus) et il s'agit d'environ 30,5 millions de caractères.

J'ai besoin de manipuler les valeurs et de les insérer dans une base de données. Que dois-je faire pour éviter les erreurs d'allocation de mémoire?


0 commentaires

7 Réponses :


2
votes

spool à un fichier. N'essayez pas de conserver toutes ces données en mémoire à la fois.


0 commentaires

3
votes
  1. Augmente memory_limit dans php.ini .
  2. lire des données en utilisant fopen () et fgets () .

0 commentaires

5
votes

Vous voudrez peut-être envisager de l'enregistrer dans un fichier temporaire, puis la lire une ligne à la fois en utilisant fgets ou fgetcsv .

De cette façon, vous évitez le premier tableau de grande taille que vous obtenez d'exploser une si grande chaîne.


0 commentaires

18
votes

php étouffe parce que cela manque de mémoire. Au lieu d'avoir Curl peupler une variable PHP avec le contenu du fichier, utilisez l'option

//pseudo, untested code to give you the idea

$fp = fopen('path/to/save/file', 'w');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_exec ($ch);
curl_close ($ch);
fclose($fp);


1 commentaires

La réponse à Stackoverflow.com/a/1342760/4668 est une meilleure que la mienne.



51
votes

comme d'autres réponses indiquées:

  • Vous ne pouvez pas avoir tout cela en mémoire
  • Une solution serait d'utiliser curlopt_file

    Mais, vous ne voudrez peut-être pas vraiment créer un fichier que vous pouvez utiliser avec des données en mémoire ... en l'utilisant dès qu'il "arrive".

    une solution possible Peut-être définir votre propre wrapper de flux et utiliser celui-ci, au lieu d'un fichier réel, avec curlopt_file

    Tout d'abord, voir:

    • stream_wrapper_register < / li>
    • La classe StreamWrapper
    • Exemple de classe enregistrée comme wrapper de flux


      Et maintenant, allons avec un exemple.

      Tout d'abord, créez notre classe wrapper de flux: xxx

      Ce que je fais est: < ul>

    • Travailler sur les morceaux de données (j'utilise Var_Dummp, mais que vous feriez vos affaires habituelles à la place) quand elles arrivent
    • Notez que vous n'obtenez pas de "lignes complètes": la fin d'une ligne est le début d'un morceau, et le début de la même ligne était à la fin du chunk précédent, vous devez donc garder certaines parties d'un morceau entre les appels vers stream_write


      Ensuite, nous enregistrons ce wrapper de flux, à utiliser avec le pseudo-protocole "test": xxx


      Et, maintenant, nous faisons notre demande de courbure, comme nous le ferions lors de l'écriture d'un fichier "réel", comme d'autres réponses suggérées: xxx

      Notez que nous ne travaillons pas avec un vrai fichier, mais avec notre pseudo-protocole.


      Ainsi, chaque fois qu'un morceau de données arrive, mystream :: stream_write sera appelé, et pourra travailler sur une petite quantité de données (quand j'ai testé, j'ai toujours été 8192 octets, quelle que soit la valeur que j'ai utilisée pour curlopt_buffersize )


      Quelques notes:

      • Vous devez tester cela plus que ce que j'ai fait, évidemment
      • My Stream_Write La mise en œuvre ne fonctionnera probablement pas si les lignes sont plus longues de 8192 octets à vous de le corriger; -)
      • Cela signifiait que quelques pointeurs, et non une solution entièrement de travail: vous devez tester (encore), et probablement coder un peu plus!

        Néanmoins, j'espère que cela aide ;-)
        amusez-vous!


2 commentaires

+1 pour cela! J'aime juste ajouter que lorsque vous traitez avec des données binaires, vous souhaitez générer directement les données de $ et ne pas la toucher du tout puisque cela le corrompre probablement.


Astucieux. Puisque curl 7.9.7 curlopt_file a été renommé curlopt_wredata , et je pense que vous pouvez maintenant faire quelque chose de similaire en utilisant curlopt_writtfunction , qui est un rappel comme votre stream_write ($ données) et enregistre le besoin de l'enveloppe de flux. Voir curl.haxx.se/libcurl/c/curl_easy_setopt.html



0
votes

NB:

"Fondamentalement, si vous ouvrez un fichier avec fopen, fcerez-le, puis dissimulez-le, ça fonctionne bien. Mais si entre fopen et fclose, vous donnez la poignée du fichier Pour courbonner pour faire de l'écriture dans le fichier, le dissimulez-vous échoue. Pourquoi Cela se passe est au-delà de moi. Je pense que cela peut être lié au bogue # 48676 "

http: //bugs.php.net/bug.php?id=49517

Soyez donc prudent si vous êtes sur une ancienne version de php. Il y a une solution simple sur cette page à doubler -Close la ressource de fichier: xxx


0 commentaires

14
votes

Darren Cook Commentaire à Pascal Martin La réponse est vraiment intéressante. Dans les versions modernes PHP + CURL, l'option CURLOPT_WRITEFunction CODE> peut être définie afin que CURL invoque un rappel pour chaque "morceau" reçu de données. Plus précisément, le "appelable" recevra deux paramètres, le premier avec l'objet d'invocation de CURL, et le second avec le morceau de données. Le fonctionnement doit revenir strallen ($ data) code> pour que CURL puis continue à envoyer plus de données.

Callables peut être des méthodes dans PHP. En utilisant tout cela, j'ai développé une solution possible que je trouve plus lisible que le précédent (bien que Pascal Martin, la réponse soit vraiment excellente, les choses ont changé depuis). J'ai utilisé des attributs publics pour la simplicité, mais je suis sûr que les lecteurs pourraient adapter et améliorer le code. Vous pouvez même avorter la demande de courbure lorsque plusieurs lignes (ou octets) ont été atteintes. J'espère que cela serait utile pour les autres. P>

<?
class SplitCurlByLines {

    public function curlCallback($curl, $data) {

        $this->currentLine .= $data;
        $lines = explode("\n", $this->currentLine);
        // The last line could be unfinished. We should not
        // proccess it yet.
        $numLines = count($lines) - 1;
        $this->currentLine = $lines[$numLines]; // Save for the next callback.

        for ($i = 0; $i < $numLines; ++$i) {
            $this->processLine($lines[$i]); // Do whatever you want
            ++$this->totalLineCount; // Statistics.
            $this->totalLength += strlen($lines[$i]) + 1;
        }
        return strlen($data); // Ask curl for more data (!= value will stop).

    }

    public function processLine($str) {
        // Do what ever you want (split CSV, ...).
        echo $str . "\n";
    }

    public $currentLine = '';
    public $totalLineCount = 0;
    public $totalLength = 0;

} // SplitCurlByLines

// Just for testing, I will echo the content of Stackoverflow
// main page. To avoid artifacts, I will inform the browser about
// plain text MIME type, so the source code should be vissible.
Header('Content-type: text/plain');

$splitter = new SplitCurlByLines();

// Configuration of curl
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://stackoverflow.com/");
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($splitter, 'curlCallback'));

curl_exec($ch);

// Process the last line.
$splitter->processLine($splitter->currentLine);

curl_close($ch);

error_log($splitter->totalLineCount . " lines; " .
 $splitter->totalLength . " bytes.");
?>


0 commentaires