5 votes

Comment passer d'un fichier fstream à l'autre sans le fermer (fichiers de sortie simultanés) - C++

J'ai un petit problème en C++ que je n'ai pas pu résoudre en naviguant sur internet. Voici mon code (extrait) :

if(File.is_open()) {
    while(!File.eof())  {
        i++;
        getline(File,Line);
        if(i>=2) {                //Skip Headers
            int CharCount=0;
            for(int CharPosition=0; CharPosition<Line.size(); CharPosition++)                       {
                if(Line[CharPosition]==',') {
                    Length=CharPosition;
                    break;
                }
            }
            NameText=Line.substr(0,Length);
            Path= Path_Folder + "\\" + NameText + ".csv";
            if(!CheckExistance(Path.c_str())) {
                fstream Text_File;
            }
            Text_File.open(Path, fstream::in | fstream::out | fstream::app);
            Text_File<<Line<<"\n";
            Text_File.close();
        }
    }
}

Ce code fonctionne bien, mais j'aimerais modifier le fait qu'il ferme le fichier Text_File à chaque fois qu'il entre dans la boucle while.

Fondamentalement, ce programme divise un gros fichier d'entrée en un grand nombre de fichiers plus petits. Au fur et à mesure que mes fichiers plus petits deviennent de plus en plus gros, l'exécution devient de plus en plus lente. (normal). Mon but est donc de laisser tous les petits fichiers ( Text_File ) ouvert en dans cette boucle while et de passer le pointeur fstream (pointeur ?) de un à l'autre.

J'ai essayé d'en changer :

...

NameText=Line.substr(0,Length);
Path= Path_Folder + "\\" + NameText + ".csv";

if(!CheckExistance(Path.c_str())) {
    fstream Text_File;
}

if(!Text_File.open()) {
    Text_File.open(Path, fstream::in |fstream::out | fstream::app);
}

Text_File<<Line<<"\n";
\\Text_File.close();

...

Mais il travaille sur le même Text_File quoi qu'il arrive NameText est. Je suppose donc que le pointeur du fstream Text_File ne change pas. Que dois-je être alors ? Reposer le pointeur ? Comment ?

Merci à tous !

Je ne suis pas sûr que ce soit pertinent, mais je travaille avec Microsoft Visual C++ 2010 Express. De plus, je ne suis pas un programmeur, ni par l'éducation ni par la vie, donc si vous pouvez m'expliquer sans trop de mots avancés, j'apprécierai.

4voto

sehe Points 123151

Il semble que vous souhaitiez jongler avec les filebuf sur un ostream objet.

Maintenant, le seul obstacle est que ostream o basic_filebuf<char> ne sont pas des types copiables, vous ne pouvez donc pas les placer directement dans une carte (par nom de fichier). Ceci est facilement contourné en créant un petit Holder type :

struct Holder {
    Holder(std::string const& path) 
        : buf(std::make_shared<std::filebuf>())
    { 
        buf->open(path.c_str(), std::ios::out | std::ios::app);
    }
    std::shared_ptr<std::filebuf> buf;
};

std::map<std::string, Holder> buffers;

Le programme complet (testé) ressemblerait maintenant à ceci :

#include <fstream>
#include <sstream>
#include <iostream>
#include <map>
#include <memory>

const std::string Path_Folder = ".";

int main()
{
    std::istream& File     = std::cin; // just for example
    std::filebuf  dummy;
    std::ostream  TextFile(&dummy);

    struct Holder {
        Holder(std::string const& path) 
            : buf(std::make_shared<std::filebuf>())
        { 
            buf->open(path.c_str(), std::ios::out | std::ios::app);
        }
        std::shared_ptr<std::filebuf> buf;
    };

    std::map<std::string, Holder> buffers;
    int i = 0;

    std::string   Line;
    while(getline(File, Line))
    {
        if (i++<2)
            continue; //Skip Headers

        auto NameText = Line.substr(0, Line.find(','));
        auto Path = Path_Folder + '/' + NameText + ".csv";

        // open, only if not allready opened
        auto found = buffers.find(NameText);
        if (end(buffers) == found)
            found = buffers.insert({ NameText, Path }).first;

        TextFile.rdbuf(found->second.buf.get());

        TextFile << Line << std::endl; // notice implicit std::flush in std::endl
    }

    // all files are automatically closed here
}

Trois notes supplémentaires :

  • sont automatiquement fermés lorsque le fichier buffers la carte sort du champ d'application.
  • vous pourriez avoir besoin d'ajouter des vidanges explicites lorsque vous passez de l'un à l'autre. rdbuf() comme ceci, si vous ne terminez pas vos lignes par un std::flush (comme avec std::endl ).
  • dummy n'existe que pour avoir un ostream que nous pouvons changer le tampon de

J'ai testé cela avec les données suivantes :

Header Row #1
Header Row #2
Jack,1,some data
Jill,2,some more data
Jack,3,not reopening :)
Jill,4,jill still receiving output
Romeo,5,someone else reporting

J'ai obtenu le résultat suivant : le voir en direct à Coliru

/tmp$ rm *.csv
/tmp$ make && ./test < input.txt && tail *.csv

g++ -std=c++11 -Wall -g test.cpp -o test
==> Jack.csv <==
Jack,1,some data
Jack,3,not reopening :)

==> Jill.csv <==
Jill,2,some more data
Jill,4,jill still receiving output

==> Romeo.csv <==
Romeo,5,someone else reporting

2voto

podkova Points 711

Note : il semble que votre Text_File est hors du champ d'application. Je suppose que vous l'avez déclaré ailleurs dans le code. Cette ligne est donc inutile :

if(!CheckExistance(Path.c_str())){fstream Text_File;}

Pour accéder à plusieurs flux de fichiers, vous pouvez utiliser cette classe simple qui utilise la fonction std::map structure des données :

#include <iostream>
#include <map>
#include <string>
#include <fstream>

class StreamWriter
{
    typedef std::map<std::string, std::fstream> StreamMap;
    static StreamMap Files;

public:
    static std::fstream& GetFile(const std::string& filename)
    {
        std::fstream& stream = Files[filename];
        if (!stream.is_open())
        {
            stream.open(filename, std::fstream::in
                   | std::fstream::out | std::fstream::app);
        }
        return stream;
    }
};

StreamWriter::StreamMap StreamWriter::Files = StreamWriter::StreamMap();

Ensuite, l'accès aux fichiers est aussi simple que :

StreamWriter::GetFile("C:/sample1.txt") << "test";

C'est tout.

1voto

Benjamin Lindley Points 51005

Ce que je ferais, c'est utiliser std::map o std::unordered_map pour faire correspondre les noms aux objets fstream.

map<string, fstream> files;

...

while(getline(File,Line)) // don't use while(File.eof())
{
    ...

    if( files.count(NameText) == 0 ) // checks for the existence of the fstream object
    {            
        files[NameText].open(Path, fstream::in | fstream::out);
    }

    files[NameText] << Line << "\n";
}

Voir aquí pour savoir pourquoi j'ai changé la condition de la boucle while.


Votre système d'exploitation peut avoir des difficultés à ouvrir autant de fichiers à la fois. Vous pourriez peut-être essayer quelque chose comme ceci.

A côté de votre carte, gardez une liste des noms des fichiers ouverts. Chaque fois que vous devez écrire dans un fichier, recherchez-le d'abord dans votre liste, supprimez-le et ajoutez-le au début de la liste. S'il n'y figure pas, ajoutez-le simplement au début de la liste. Vérifiez que le fichier est ouvert. Si ce n'est pas le cas, essayez de l'ouvrir. Si l'ouverture échoue, supprimez un à un les éléments de la fin de la liste, fermez le fichier correspondant à cet élément et essayez à nouveau d'ouvrir le fichier en cours. Répétez l'opération jusqu'à ce que l'ouverture du fichier réussisse.

Cela permet de s'assurer que les fichiers les plus fréquemment écrits restent en tête de liste et restent ouverts. Les fichiers les moins fréquemment écrits se retrouveront à la fin de la liste et seront finalement fermés. La recherche du fichier dans la liste n'est pas optimale (O(n)), mais comme il s'agit ici d'écrire dans des fichiers, ce qui est une opération beaucoup plus coûteuse, vous ne devriez pas constater de baisse de performance.

0voto

Sabuncu Points 1067

Vous essayez de réutilisation el Text_File fstream. Pour ce faire, vous devez effectuer une close() pour vider le flux, une fois que vous avez fini d'écrire dans un fichier csv. Veuillez consulter cette question : C++ : puis-je réutiliser fstream pour ouvrir et écrire plusieurs fichiers ?

En outre : Voici ma recherche Google pour cette question : http://goo.gl/Oy5KKM

0voto

Code-Apprentice Points 18086

Il convient de noter que Text_File est une variable et, comme toutes les variables, vous pouvez en avoir plusieurs du même type. Si vous devez gérer plusieurs fichiers différents, vous pouvez même utiliser std::fstream dans l'un des conteneurs standard tels que std::vector o std::map . Vous devriez également envisager de diviser votre code en parties plus petites et plus faciles à gérer. Par exemple, vous pouvez créer une fonction qui prend un élément std::fstream& comme paramètre. Cela permet au reste du programme de contrôler quel std::fstream& est utilisé à un moment donné. Je vous suggère fortement d'examiner différentes options de conception pour vous aider à organiser votre code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X