Outils pour utilisateurs

Outils du site


copie_partielle_bloc

Copie partielle d'un fichier texte par bloc binaire

Objectif

On a un fichier texte de 200000 (deux cent mille) lignes, et on veut en copier 65000 à partir de la ligne 130000 (cent trente mille).

La solution classique est de lire toutes les lignes avec readline() jusqu'à la ligne 130000, puis de lire/écrire les 65000 lignes suivantes pour les copier. Bon, ça fait beaucoup de travail à l'exécution et ça va consommer beaucoup de mémoire puisque Python va utiliser une nouvelle zone mémoire à chaque ligne lue. Dans le meilleur des cas, le ramasse-miette va se déclencher, sinon, il y aura une exception pour erreur de mémoire.

L'objectif est donc de faire ce travail en utilisant exclusivement les lectures/écritures binaire par bloc. On verra que comme les blocs sont de même taille, Python reprendra facilement les mêmes zones mémoire.

Petite restriction du code ci-dessous: ça marche, à condition que l'encodage des caractères type unicode (=caractères codés sur plusieurs octets) ne contienne pas d'octet identique à la fin de ligne cherchée.

Trouver l'adresse sur disque du début d'une ligne de texte

Le principe est très simple: on va lire le fichier en mode binaire ('rb'), et on va compter les caractères de fin de ligne ('\n' par défaut), jusqu'à ce que le numéro de ligne soit atteinte. On retournera alors la position de l'octet correspondant dans le fichier sur disque.

Voilà le code proposé:

def debligne(nf, n, fdl='\n', tbuf=16384):
    """renvoie l'adresse du début de la ligne d'indice n"""
    if n == 0:
        return 0  # l'adresse de la ligne 0 est 0
    f = open(nf, 'rb')
    c = 0  # compteur de fins de ligne fdl
    while True:
        buf = None
        buf = f.read(tbuf)
        #print id(buf) # pour vérifier que buf pointe sur la même zone mémoire
        if len(buf)==0:
            # la ligne n n'a pas été trouvée avant la fin du fichier
            f.close()
            return -1
        i = -1  # indice de caractères dans le buffer buf
        while True:
            i += 1
            i = buf.find(fdl, i)  # recherche du fin de ligne suivant à partir de i
            if i<0:
                # pas trouvé: il faut lire et essayer avec le buffer suivant
                break
            else:
                # on a trouvé un nouveau caractère de fin de ligne
                c += 1  # on incrémente le compteur des fins de ligne
                if c == n:
                    # on a trouvé! 
                    t = f.tell()-tbuf+i+1
                    f.close()
                    return t

Si le numéro de ligne demandé dépasse le nombre de lignes du fichier, la fonction retourne -1.


Testons ce code. On va fabriquer un fichier de 200000 lignes, qui comportera comme contenu: '0', '1', '2', …, '199999'. On pourra donc facilement vérifier que la ligne lue est bien celle qu'on voulait.

nf = 'texte.txt'
f = open(nf, 'w')
for i in xrange(0,200000):
    x = "%d" % (i)
    f.write(x + '\n')
f.flush()
f.close

Appliquons maintenant notre fonction:

n = 130000
t = debligne(nf, n)
if t>=0:
    f = open(nf, 'r')
    f.seek(t)
    ch = f.readline().rstrip()
    print u"La ligne numéro", n , u"commence à l'octet", t, u"et contient: ", '"'+ch'"'
    f.close()
else:
    print u'numéro de ligne trop grand'

Ce qui affiche:

La ligne numéro 130000 commence à l'octet 928890 et contient: "130000" 

Copier les lignes n1 à n2 d'un fichier texte par bloc binaire

Pour copier les lignes n1 à n2 (non inclue) par blocs binaires, le principe est simple: on va utiliser la fonction précédente pour calculer les adresses sur disque du début de la 1ère ligne et celle de la dernière ligne, et on va copier par bloc tous les octets entre ces 2 limites.

Voilà le code:

import os
 
def copiebloc(nf1, nf2, n1=0, n2=None, fdl='\n', tbuf=16384):
    """Copie tout ou partie d'un fichier texte par bloc binaire"""
    t1 = debligne(nf1, n1, fdl, tbuf)
    if n2==None:
        t2 = os.path.getsize()
    else:
        t2 = debligne(nf1, n2, fdl, tbuf)
    tt = t2-t1
    f1 = open(nf1, 'rb')
    f1.seek(t1)  # on met le curseur de lecture au début de la 1ère ligne à lire
    f2 = open(nf2, 'wb')
    while tt>0:
        buf = None
        buf = f1.read(tbuf)
        lbuf = len(buf)
        if lbuf>0:
            if lbuf>tt:
                buf = buf[:tt]
                lbuf = tt
            f2.write(buf)
            tt -= lbuf
    f2.flush()
    f1.close()
    f2.close()

A noter que si on ne précise pas les numéros de lignes (ni n1, ni n2), le fichier texte est copié en totalité en mode binaire par bloc.

Appliquons: on va recopier 65000 lignes à partir de la ligne 130000 pour notre fichier de test précédent:

nblignes = 65000
ligne1 = 130000
 
source = 'texte.txt'
destination = 'texte2.txt'
 
copiebloc(source, destination, ligne1, ligne1+nblignes)

Et c'est tout: c'est très rapide, et vous pouvez vérifier avec un bon éditeur de texte (comme notepad++ sous Windows) que le nouveau fichier texte2.txt contient bien les 65000 lignes demandées!


Amusez-vous bien!

copie_partielle_bloc.txt · Dernière modification: 2010/03/27 18:36 par tyrtamos

Outils de la page