2013-07-26 1 views
5

У меня есть небольшая проблема на C++, которую я не мог решить, просматривая онлайн. Вот мой код (извлеченный):Как я могу переключаться между файлами fstream без их закрытия (одновременные выходные файлы) - C++

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(); 
     } 
    } 
} 

Этот код работает отлично, но я хотел бы изменить тот факт, что она закрывает Text_File каждый раз, когда он идет в то время цикла.

В принципе, эта программа разбивает большой входной файл на множество небольших файлов. Поскольку мои меньшие файлы размером становятся все больше и больше, выполнение выполняется медленнее и медленнее (обычный). Моя цель состоит в том, чтобы все мелкие файлы (Text_File) открылись в этом цикле while и просто переключили указатель на указатель (указатель?) От одного до другого.

Я попытался изменить как:

... 

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(); 

... 

Но это не работает на том же Text_File независимо от того, что NameText есть. Поэтому я предполагаю, что указатель fstream Text_File не изменяется. Что мне нужно тогда? Поставить указатель? Как?

Спасибо, все!

Не уверен, что это актуально, но я работаю с Microsoft Visual C++ 2010 Express. Кроме того, я не программист ни по образованию, ни по жизни, поэтому, если вы можете объяснить это без слишком сложных слов, я буду признателен.

+1

Как о создании 'File' массив? – wallyk

+0

Я чувствую, что указатель на один (или каждый) из уже объявленных 'Text_File' будет работать –

ответ

4

Похоже, вы хотели бы жонглировать filebuf s на объекте ostream.

Теперь единственное препятствие состоит в том, что ostream или basic_filebuf<char> не являются типами для копирования, поэтому вы не можете напрямую их поместить на карту (по имени файла). Это легко обойти, создав небольшой Holder типа:

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; 

Теперь всю программу (проверено) будет выглядеть следующим образом:

#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 
} 

еще три ноты:

  • файлы получают автоматически закрывается, когда карта buffers выходит за пределы области действия.
  • Возможно, вам потребуется добавить явные сбросы при переключении rdbuf(), если вы не закончите свои строки неявным std::flush (например, с std::endl).
  • dummy существует только иметь ostream объект, который мы можем переключить буфер

Я испытал это со следующим входом:

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 

Теперь, я получил следующий вывод: see it live at 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 
+0

Исправлено и упрощено после того, как вы поняли, что хотите создать файлы, если они еще не существовали. ** И ** удалили использование файловой системы Boost (поскольку «CheckExistance» в конце концов не понадобился). ** [Смотреть все вживую в Колиру] (http://coliru.stacked-crooked.com/view?id=1af27de002d18cf2ded1e05f58942835-9c316e88ae784971c383263e9035353b) ** – sehe

+0

Спасибо, Се! – Vince

+0

Улучшенный код, который будет менее путать с файловыми файлами. Также фиксированная потенциальная многооткрытая проблема; (Я хотел «map :: emplace», но GCC еще не получил его ...) [Live on Coliru] (http://coliru.stacked-crooked.com/view?id=9c388874ad76178c7bcdd49beeae01a9-9c316e88ae784971c383263e9035353b) – sehe

0

Вы пытаетесь использовать повторно использоватьText_File fstream. Для этого вам нужно сделать close(), чтобы очистить поток, после того как вы закончите запись в файл csv. Пожалуйста, смотрите на этот вопрос: C++ can I reuse fstream to open and write multiple files?

также: Вот мой поиск Google по этому вопросу: http://goo.gl/Oy5KKM

1

Что я хотел бы сделать, это использовать std::map или std::unordered_map для отображения имен 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"; 
} 

См here, почему я изменил условие цикла.


У вашей ОС может быть проблема с тем, что у нее много открытых файлов одновременно. Возможно, вы могли бы попробовать что-то вроде этого.

Наряду с вашей картой сохраните список имен открытых файлов. Каждый раз, когда вам нужно писать в файл, сначала найдите его в своем списке, удалите его и добавьте в начало списка. Если его нет, просто добавьте его в начало списка. Убедитесь, что файл открыт. Если это не так, попробуйте открыть его. Если открытие не удается, то один за другим удалите элементы из задней части списка, закройте соответствующий файл и снова попытайтесь открыть текущий файл. Повторяйте, пока не откроется файл.

Выполнение этого гарантирует, что наиболее часто записанные файлы хранятся в передней части списка и остаются открытыми. Менее часто записанные файлы будут перемещаться назад, и в конечном итоге будут закрыты. Поиск файла в списке не является оптимальным (O (n)), но поскольку мы имеем дело с написанием файлов здесь, что является гораздо более дорогостоящей операцией, вы не должны замечать какого-либо перфоманса.

+0

Привет, Бенджамин, спасибо. Я попробовал ваше решение. Программа переходит в условие if и добавляет NameText к карте fstream, но физически не создает файл NameText. Моя папка пуста. Есть идеи? – Vince

+0

Привет, Бенджамин, я попробовал ваш код, и теперь он работает (у меня было приложение ios: app в открытой строке). Но, кажется, пропустить некоторые файлы, и я не понимаю, почему. Я получил 508 файлов вместо 545. Когда я добавляю строку «files [NameText] .close();» на этом конце цикла, пока он работает, и я получаю 545 файлов, но тогда я нахожусь в той же точке, что и раньше: файлы открываются и закрываются каждый раз. – Vince

+0

@Vince: Я добавил возможное объяснение и решение. –

0

Обратите внимание, что Text_File является переменной и, как и все переменные, вы можете иметь более одного типа с одним и тем же типом. Если вам нужно управлять несколькими различными файлами, вы можете даже использовать std::fstream в любом из стандартных контейнеров, таких как std::vector или std::map. Кроме того, вы должны рассмотреть возможность разбить свой код на более мелкие более управляемые части. Например, вы можете создать функцию, которая принимает параметр std::fstream&. Это позволяет остальной части программы контролировать, какой std::fstream& используется в любой момент времени. Я настоятельно рекомендую вам взглянуть на различные варианты дизайна, чтобы помочь организовать ваш код.

2

Примечание: похоже, что ваш Text_File вышел из сферы действия. Думаю, вы объявили это где-то еще в коде. Таким образом, эта линия не имеет смысла:

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

Для доступа нескольких файлов потоков, вы можете использовать этот простой класс, который использует структуру std::map данных:

#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(); 

Затем, доступ к файлам так просто as:

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

Всё.

+0

-1 Вы не можете поместить объекты 'fstream' в' map', подобные этому (потому что они не могут быть скопированы/перемещены). См. Мой ответ на полностью протестированное решение. – sehe

+0

. Ой, я только что попытался скомпилировать этот код в gcc, и вы правы ... Однако Visual C++ версия 'std :: map 'компилирует штраф, никаких ошибок, никаких предупреждений. – podkova

+0

Интересно :) Он не должен компилироваться. Хотя, поскольку C++ 11 'fstream' должен быть подвижным. Я отменяю свой -1, хотя, потому что вы его протестировали, и это может сработать для кого-то. – sehe

0

Свидетельство о проверке существования не имеет эффекта - как уже упоминалось. Возможно, ваше намерение было сделать что-то вроде этого:

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(); 
} 

В fstream в пределах объема, если заявление будет скрыть тот, который вы должны иметь в наружной области. Кроме того, закрытие необязательно - поток будет закрыт, когда он выходит за рамки.

Смежные вопросы