Publié le 27/07/2016
Auteur Fobec
Réseaux sociaux
0 partages
0 tweets
1 plus
0 commentaires

Lire une ligne dans un fichier texte

entrain d'analyser les textes pour en extraire des données, je veux partager avec vous une méthode pour charger une ligne à partir d'un fichier avec le langage PHP. Concrètement, le but est de récupérer un ensemble de caractère placé entre deux retour à la ligne en connaissant l'index de la ligne. La recherche est effectuée sur des textes de grandes tailles parfois supérieures à 1 giga donc les fonctions file() et file_get_contents() ne sont pas utilisables.
Pourquoi ?
les deux fonctions se basent sur le chargement de l'ensemble du fichier dans la mémoire. Puisqu'il s'agit de fichiers texte de grande taille, ça risque de coincer sur la RAM disponible.

Charger une ligne sans utiliser un index

Le but de cet article est de trouver une méthode et un script PHP permettant de lire le plus rapidement que possible une ligne dans un fichier texte. Pour l'exemple, mettons que l'on veuillez charger la ligne numéro 4 000 dans un fichier texte. En langage informatique, le script PHP doit repérer le 4 000 ème retour à la ligne dans le fichier texte puis retourner les caractères contenus jusqu'au prochain retour de chariot.
Pour avoir des mesures et un benchmark qui tiennent la route, nous simulons 4200 chargements de lignes.

<?php
$t0 = microtime(true);
$file = 'corpus.txt';
for ($i = 0; $i < 4200; $i++) {
    //ouverture du fichier corpus en mode lecture seule
    $fp = @fopen($file, "r");
    if ($fp) {
        //probablement inutile
        rewind($fp);
        $count_line = 0;
        //Boucle sur toutes les lignes du fichier
        while (!feof($fp)) {
            //Lire la ligne, c-a-d jusqu'au prochain line break
            $lines = fgets($fp);
            //Test du n° de ligne
            if ($count_line == $i) {
                $line_contents = fread($fp, $len);
                break;
            }
            $count_line++;
        }
    }
 fclose($fp);
}
echo (microtime(true) - $t0);
?>

Temps de traitement: 3.2104468345642 secondes
Qu'a fait le script PHP pour nous retourner les lignes ?
Le script a cherché dans le fichier texte les lignes 1, 2, 3, 4 .... jusqu'à 4200. Pour ce faire, à chaque fois, les fonctions PHP parcourent l'ensemble du fichier texte pour trouver la bonne ligne puisque on ne sait pas où se trouver le retour de chariot n° 1, 2, 3, ... dans le fichier texte.

Parcourir un fichier texte à l'aide d'un index

Afin d'optimiser l'extraction d'une ligne à partir d'un fichier texte, faisons un test avec un index. Le principe est de mémoriser l'emplacement des retours chariots dans le corpus. Ainsi, à chaque lecture, le script PHP peut se placer directement à la position à lire sans avoir à parcourir l'ensemble du fichier texte.

Création d'un index

En amont, le texte est analysé puis la position de chaque retour chariot est mémorisée. Pour le stockage de ces données, nous allons utiliser un base Sqlite et un fichier.

Pour la base de données Sqlite, les fonctions sont rassemblées dans une classe PHP nommée Map_corpus
Structure de la DB Map_corpus
CREATE TABLE IF NOT EXISTS TB_MAP (
MAP_LINE_ID INTEGER PRIMARY KEY AUTOINCREMENT,
MAP_OFFSET_START INTEGER NOT NULL,
MAP_OFFSET_END INTEGER NOT NULL
)

Voyons comment créer l'index :

<?php
//Class Sqlite
$map_corpus = new Map_corpus();
$map_corpus->connect();
 
$count_line = 0;
$offset_start = 0;
$offset_end = 0;
 
//Fichier texte à analyser
$fp = @fopen('corpus.txt', "r");
//Fichier index
$fwrite = @fopen('corpus.idx', "w");
if ($fp) {
    $map_corpus->transaction_start();
    while (!feof($fp)) {
        $lines = fgets($fp);
        $offset_end+=strlen($lines);
        $count_line++;
        //Insérer dans la base de donnée
        $map_corpus->append_line_index($offset_start, $offset_end);
        $rec = str_pad((string) $offset_start, 10, '0', STR_PAD_LEFT);
        $rec.= str_pad((string) $offset_end, 10, '0', STR_PAD_LEFT);
        //Ecrire dans le fichier index
        fwrite($fwrite, $rec);
        $offset_start = $offset_end;
    }
    $map_corpus->transaction_commit();
    fclose($fwrite);
    fclose($fp);
}
?>

L'utilisation d'un index au format fichier texte et en même temps au format Sqlite sert uniquement à tester les 2 systèmes, à savoir lequel est le plus performant.

Lecture d'une ligne avec un index

L'index que ce soit sous forme d'une base de donnée ou que ce soit sous forme d'un fichier, nous permet de savoir à l'avance où se trouve exactement chaque ligne dans le texte.

<?php
//Méthode avec l'index Sqlite
$t0 = microtime(true);
$map_corpus = new Map_corpus();
$map_corpus->connect();
for ($i = 0; $i < 4200; $i++) {
    $fp = @fopen($src_file, "r");
    if ($fp) {
        $n = $map_corpus->get_offset($i); //debut
        rewind($fp);
        fseek($fp, $n['MAP_OFFSET_START']);
        $len = intval($n['MAP_OFFSET_END'] - $n['MAP_OFFSET_START']);
        $contents = fread($fp, $len);
    }
    fclose($fp);
}
echo (microtime(true) - $t0) . '<br>';
 
//Méthode avec un fichier index
$t0 = microtime(true);
for ($i = 0; $i < 4199; $i++) {
    $fwrite = @fopen('corpus.idx', "r");
    $fp = @fopen('corpus.txt', "r");
    if ($fwrite) {
        rewind($fwrite);
        fseek($fwrite, $i * 20);
        $contents = fread($fwrite, 20);
        $offset_start = (int) substr($contents, 0, 10);
        $offset_end = (int) substr($contents, 10, 10);
        rewind($fp);
        fseek($fp, $offset_start);
        $contents = fread($fp, ($offset_end - $offset_start));
    }
    fclose($fwrite);
    fclose($fp);
}
echo (microtime(true) - $t0) . '<br>';
?>

Résultats de la lecture de 4200 lignes de texte
0.20169281959534 pour la base de donnée.
0.065440893173218 pour le fichier index.

L'utilisation d'un index pour lire une ligne est environ 50 fois plus rapide que la lecture directe. Le résultat était prévisible puisque le script PHP se contente de lire le corpus à une position déterminée sans avoir à parcourir l'ensemble du texte.

S'agit-il d'un défi de Geeks pour gagner quelques millisecondes ?
En fait, je travaille sur des textes de grande taille et chaque milliseconde compte pour éviter d'attendre des plombes pour avoir le résultat du script PHP.
Bref, si vous avez à extraire une ligne d'un fichier texte de plusieurs centaines de Mo ou de plusieurs giga, pensez à créer un index pour réduire considérablement le temps de lecture d'une ligne.

Ajouter un commentaire

Les champs marqués d'un * sont obligatoires, les adresses emails se sont pas publiées.

A lire aussi

Réseaux sociaux
Présentation de l'article
Catégorie
php5 - script
Mise a jour
27/07/2016
Visualisation
vu 2533 fois
Public
Internaute
Auteur de la publication
Fobec
Admin
Auteur de 261 articles
|BIO_PSEUDO|
Commentaires récents

Publié par ric dans CMS

c\'est la première fois que j\'utilise ce logiciel

Publié par claude77 dans tuto

Super application, fonctionne bien
exemple sur une image

Publié par Coll dans java

Excuse moi, mais pourrais tu expliquer un peu plus precisement ce que tu fais lorsque tu convertis en gris je comprends l'idee mais pas ce que chaque action fait precisement et le cacth a la fin. Parc...

Publié par chuchunain dans tuto

Petite correction dans votre code. Le premier graphe genere avec 15 valeurs aleatoires devrait faire appel au graphe "myGraph" et non "graph" sinon l'objet canvas ne recoit rien. m...

Publié par Josalado dans CMS

je n'arrive pas à installer le logiciel <br>Message d'erreur : l'assistant d'installation n'a pas pu créer le dossier C:\program Files\Olibul <br>...