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.
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"
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!