Publié le 27/07/2016
Auteur Fobec
Réseaux sociaux
0 partages
0 tweets
0 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 6066 fois
Public
Internaute
Auteur de la publication
Fobec
Admin
Auteur de 267 articles
|BIO_PSEUDO|
Commentaires récents

Publié par Fokoneh dans java

Je teste d'abord, Je reviens

Publié par ZINEDDINE dans CMS

SaLUT Monsieur
tu peux avec ce deux mot pass ouvrir tous les tables paradox
1-jIGGAe
2-cupcdvum
respecter le majjuscul
bonne chance .

Publié par vieux dans CMS

et comment ce le procurent ton ?

Publié par pidjey dans php5

J'ai bien lu mais c'est vraiment trop fort et trop dur pour le moment, je sais pas vraiment comprendre vos syntaxes, elles sont enormes et moi, je ne suis qu'encore un debutant en PHP et il faudrait p...

Publié par Souti dans php5

Merci pour ce code. Trs utile.

Pour mon cas, il a fallu que j'ajoute une } avant ?> le pour fermer le for each.