[c++] écriture dans un fichier, démystification [résolu]

Démarré par compositeur, 05 Septembre 2012 à 11:08

0 Membres et 1 Invité sur ce sujet

05 Septembre 2012 à 11:08 Dernière édition: 06 Septembre 2012 à 16:38 par compositeur
Bonjour à tous,

  Je demande l'aide des programmeurs de ce forum via ce topic, effectivement, depuis quelques temps, je suis confronté à un problème en C++.

  Je m'amuse à coder un petit programme en c++ depuis quelques temps, qui nécessite de manipuler des fichiers. J'utilise la bibliothèque QT, donc pas de problème, il existe via cette bibliothèque des classes toutes faites pour manipuler facilement les fichiers. Mais voila : à un moment, dans mon programme, j'ai besoin d'écrire en plein milieu d'un fichier, sans écraser le reste. Après quelques recherches, je suis arrivé à la conclusion que cela était impossible, je pouvais écrire à la fin d'un fichier, à la place d'un fichier... mais pas en plein milieu.

  J'avais donc face à moi deux solutions :
  1) Copier le début du fichier dans un autre fichier temporaire. Placer à la suite les données dont j'avais besoin, puis y placer ensuite la fin du fichier d'origine. Ensuite, supprimer le fichier d'origine et renommer le fichier temporaire.
  2) Placer tout le fichier, ligne par ligne, dans un vector<string>, y apporter les modifications voulues, puis vider complètement le fichier en y écrivant à la place le contenu final de mon vector.

  Voici donc toutes mes questions :
  Pourquoi ne peut-on pas écrire au milieu d'un fichier ? A quoi sert la fonction ostream::seekp ?
  Laquelle, de mes deux méthodes, est la plus optimisée -en terme de temps d'execution- ?
  Existe-t-il des bibliothèques proposant des méthodes simples afin d'écrire au milieu d'un fichier ?
  Dans ma solution 2), j'ai décidé de stocker le contenu de mon fichier dans un vector<string>. Sachant que les fichiers que je manipule ont des tailles généralement comprises entre 50ko et 10Mo, est il possible de tout stocker dans un string ? Y a-t-il une limite de taille ?

Alors, pour ta première question, lorsqu'on écrit dans un fichier déjà existant, il n'y a aucune copie de fichier de faite ni rien du tout, donc aucune perte mystérieuse de performance. Si on écrit "au milieu" d'un fichier, soit on écrase les données après la position où on a commencé à écrire, soit ça implique que les données soient d'abord copiées ailleurs et il y a donc une perte de performance. Et donc si on veut écrire dans un fichier sans perdre de données, on crée en général un fichier temporaire, donc on est dans le cas de ta première solution.

La fonction seekp sert à aller à une position donnée dans un fichier. Typiquement, la position est récupérée auparavant avec la fonction tellp. En gros, ça sert à revenir à un endroit du fichier marqué au préalable.

Pour savoir la quelle de tes deux méthodes est la plus optimisée, tu devras faire un benchmark à la main. Personnellement, je dirais la première : tu te contentes de lire un fichier et d'écrire dans un autre. Ta seconde méthode implique d'écrire dans des chaînes puis de réécrire dans un fichier, donc tu as deux fois plus d'opérations de faites.

Je ne sais pas s'il existe des bibliothèques prévues à cet effet. Tu peux chercher voir si une des nombreuses bibliothèques de Boost fournit une fonctionnalité similaire.

En général, les strings ont une limite de taille que tu peux récupérer via leur méthode string::max_size. Si tu essayes de mettre plus que ça dans une string, les fonctions qui le font risquent de retourner une exception.

05 Septembre 2012 à 18:22 #2 Dernière édition: 05 Septembre 2012 à 18:29 par compositeur
Merci Morwenn ;)
Quelques petits détails me turlupinent cependant encore un peu.

Déja, qu'est-ce qu'un benchmark ?
Ensuite, j'ai lu que les opérations qui consistaient à lire/écrire dans des fichiers étaient des opérations très longues et coûteuses... Comment cela se peut il donc que la solution 1 soit plus rapide, au niveau du temps d'exécution, que la solution 2 ?
Et enfin, en lisant la documentation sur c++ reference de la fonction ostream::seekp, j'ai vu cet exemple :
#include <fstream>
using namespace std;

int main () {
 long pos;

 ofstream outfile;
 outfile.open ("test.txt");

 outfile.write ("This is an apple",16);
 pos=outfile.tellp();
 outfile.seekp (pos-7);
 outfile.write (" sam",4);

 outfile.close();

 return 0;
}


Visiblement, à la fin de l'exécution de ce code, le fichier est censé contenir "This is a sample". Donc, on a bien à faire ici à une écriture dans un fichier qui a eu lieu "au milieu", sans perte d'information  (et à ce propos, où est passé le 'n' de "an" ?) ?

Un benchmark, c'est juste un test de vitesse en fonction de certains paramètres. Là en gros, je te disais juste de comparer toi-même la vitesse d'exécution ; c'est la seule méthode qui marche vraiment.

Dans l'exemple que tu donnes, il y a perte d'informations en fait : dans "this is an apple", "n ap" est remplacé par " sam" pour faire "this is a sample". Les données ont clairement été écrasées et non insérées.

Après, oui, les opérations d'entrées/sorties sont toujours coûteuses, mais là, dans tous les cas, tu devras réécrire dans un fichier à la fin, quoi que tu fasses. Sauf que dans la solutions avec les string, tu dois en plus d'abord tout réécrire dans des strings.

Après, si tu veux accélérer les opérations d'entrée/sortie, tu peux précéder ton code par
std::ios_base::sync_with_stdio(false);

En fait, en C++, par défaut, cette option est mise à true, c'est-à-dire que tous les flux d'entrée-sortie ont l'obligation de convertir les arguments d'une certaine manière et de faire appel aux fonctions C de cstdio. Si tu désactives la synchronisation, la compilateur se démerde et en général se débrouille pour faire ça de manière bien plus rapide. Cette simple ligne accélèrera de toutes façons ton code ;)

Et imaginons que je veuille écrire au milieu d'un document, et qu'écraser les données à l'endroit où j'écris m'importe peu, du moment que je garde le début et la fin, je peux utiliser seekp ? Si oui, j'imagine que cette opération est plus rapide que mes deux solutions précédemment envisagées ?

Si l'option existait aussi simplement, je te l'aurais déjà donnée dès le premier post^^

Comme je te dis, la technique générale dans ces cas-là, c'est de recopier le fichier d'entrée dans un fichier temporaire jusqu'à l'endroit voulu, d'ajouter ce que tu veux dans le fichier temporaire, puis d'écrire la fin du fichier d'entrée par le fichier temporaire. Et puis après, de remplacer le fichier d'entrée par le fichier temporaire.

D'accord, mais dans ce cas qu'est-ce qui va se passer en utilisant la méthode donnée en exemple de seekp ? C'est viable sur des gros fichiers ? Peu importe sur ça m'écrase des données au milieu du fichier. Si j'ai bien compris, ça fonctionne un peu comme la touche "inser" quand on est dans un traitement de texte ? Si c'est le cas, alors ça serait pas mal, car techniquement, mon programme se fiche pas mal de ce qu'il y a dans le fichier, il faut juste qu'il garde grosso modo la même allure...

05 Septembre 2012 à 22:07 #7 Dernière édition: 05 Septembre 2012 à 22:10 par Morwenn
Si tu n'as rien à faire d'écraser les données, alors en effet, le plus simple est d'écrire directement dedans, donc de l'ouvrir en mode read/write, de lire jusqu'aux endroits qui t'intéressent et d'écrire directement par-dessus.

De cette façon, je suis assuré que toute la fin du fichier ne sera pas effacée ?

Je le dis et je le répète, tout ce que tu as à savoir, c'est qu'écrire au milieu d'un fichier n'insère aussi donnée, mais ça se contente d'écrire par-dessus les données déjà existantes. Maintenant, à toi de voir si ça t'emmerde ou non -______-