Чтобы повысить производительность чтения из файла, я пытаюсь прочитать все содержимое большого (несколько МБ) файла в память, а затем использовать istringstream для доступа к информации.
У меня вопрос: как лучше всего прочитать эту информацию и «импортировать ее» в поток строк? Проблема с этим подходом (см. Ниже) заключается в том, что при создании строкового потока буферы копируются, а использование памяти удваивается.
#include <fstream>
#include <sstream>
using namespace std;
int main() {
ifstream is;
is.open (sFilename.c_str(), ios::binary );
// get length of file:
is.seekg (0, std::ios::end);
long length = is.tellg();
is.seekg (0, std::ios::beg);
// allocate memory:
char *buffer = new char [length];
// read data as a block:
is.read (buffer,length);
// create string stream of memory contents
// NOTE: this ends up copying the buffer!!!
istringstream iss( string( buffer ) );
// delete temporary buffer
delete [] buffer;
// close filestream
is.close();
/* ==================================
* Use iss to access data
*/
}
Возможно, вам следует вместо этого поискать в файлах с отображением памяти.





std::ifstream имеет метод rdbuf(), который возвращает указатель на filebuf. Затем вы можете «вставить» этот filebuf в stringstream:
#include <fstream>
#include <sstream>
int main()
{
std::ifstream file( "myFile" );
if ( file )
{
std::stringstream buffer;
buffer << file.rdbuf();
file.close();
// operations on the buffer...
}
}
Обновлено: как отмечает Мартин Йорк в комментариях, это может быть не самое быстрое решение, поскольку stringstreamoperator<< будет читать символ filebuf символ за символом. Возможно, вы захотите проверить его ответ, где он использует метод ifstreamread, как вы это делали раньше, а затем установил буфер stringstream так, чтобы он указывал на ранее выделенную память.
Привет, Люк, я согласился с вашим предложением ... манипуляция rdbuf - это правильный путь! Но разве у вашего решения нет такой же проблемы? Разве вы не создаете 2 копии одного и того же буфера хотя бы на мгновение?
Поскольку к моменту, когда operator << () видит результат rdbuf (), это всего лишь буфер потока, без понятия файлового буфера на данный момент, он не может найти его длину и, следовательно, должен использовать цикл для чтения 1 символа вовремя. Также внутренний буфер строкового потока (std :: string) должен быть изменен в соответствии с размером вставленных данных.
Кажется, удаляются символы новой строки, которые мне нужны.
file.close(); нужен?
@artm Это не обязательно, но лучше закрыть дескрипторы файлов, как только вы закончите их использовать. Без явного вызова close файл был бы закрыт, когда file был бы уничтожен (в конце его области видимости, то есть в конце main). Лучшим подходом, вероятно, было бы ограничить область действия file.
Мне это кажется преждевременной оптимизацией. Сколько работы делается в обработке. Предполагая, что настольный компьютер / сервер современный, а не встроенная система, копирование нескольких МБ данных во время инициализации довольно дешево, особенно по сравнению с чтением файла с диска в первую очередь. Я бы придерживался того, что у вас есть, измерял систему, когда она будет завершена, и решаю, стоит ли того потенциального прироста производительности. Конечно, если памяти мало, это во внутреннем цикле или часто вызываемой программе (например, раз в секунду), которая меняет баланс.
Еще нужно помнить, что файловый ввод-вывод всегда будет самой медленной операцией. Решение Люка Турэля правильное, но есть и другие варианты. Чтение всего файла в память за один раз будет намного быстрее, чем чтение по отдельности.
В ПОРЯДКЕ. Я не говорю, что это будет быстрее, чем чтение из файла
Но это метод, при котором вы создаете буфер один раз, а после того, как данные считываются в буфер, используйте его непосредственно в качестве источника для строкового потока.
N.B.It стоит упомянуть, что std :: ifstream буферизуется. Он считывает данные из файла (относительно большими) кусками. Потоковые операции выполняются с буфером, только возвращающимся в файл для следующего чтения, когда требуются дополнительные данные. Поэтому, прежде чем закачивать все данные в память, убедитесь, что это горлышко бутылки.
#include <fstream>
#include <sstream>
#include <vector>
int main()
{
std::ifstream file("Plop");
if (file)
{
/*
* Get the size of the file
*/
file.seekg(0,std::ios::end);
std::streampos length = file.tellg();
file.seekg(0,std::ios::beg);
/*
* Use a vector as the buffer.
* It is exception safe and will be tidied up correctly.
* This constructor creates a buffer of the correct length.
*
* Then read the whole file into the buffer.
*/
std::vector<char> buffer(length);
file.read(&buffer[0],length);
/*
* Create your string stream.
* Get the stringbuffer from the stream and set the vector as it source.
*/
std::stringstream localStream;
localStream.rdbuf()->pubsetbuf(&buffer[0],length);
/*
* Note the buffer is NOT copied, if it goes out of scope
* the stream will be reading from released memory.
*/
}
}
@ Мартин Йорк, как вы узнаете эти детали, читаете ли вы или исследуете, когда сталкиваетесь с проблемой, и, в свою очередь, вы узнаете все эти детали? Большое спасибо, bdw.
@Gollum: Нет, это всего лишь детали, полученные из двух областей. 1) Постоянное использование потоковых классов. 2) Реализовав свои собственные потоковые классы. Number (2) заставляет вас много читать о том, как должен работать поток, потому что вы хотите, чтобы он работал для вашего потока так же, как он работает для стандартных потоков (чтобы вы могли повторно использовать библиотеку STL функции для стандартных потоков). Единственный неинтересный бит из вышеизложенного - это изменение того, как работает буфер потока.
Можете ли вы предложить книгу или некоторые ресурсы, я хочу глубоко понять стандартную библиотеку шаблонов (не только ее использование, но и то, как она на самом деле работает внутри)
Я не думаю о том, что «поскольку char - это тип данных POD, он не инициализируется». правильно. Конструктор фактически имеет два аргумента, второй - значение, которым инициализируются элементы. По умолчанию в нашем случае это T() или char(), что означает 0. Таким образом, все элементы должны быть равны 0.
-1, этот метод (basic_stringbuf :: setbuf) определяется реализацией.
@ybungalobill: Да, так. Определенная реализация не является «Неопределенной»
@Martin: Вы правы, что это не «неопределенное поведение», но оно не переносимо, поэтому я не могу назвать его «стандартным решением C++».
@ybungalobill: Я не понимаю, как вы пришли к такому выводу. Судя по моей документации, все будет работать, как ожидалось. См. 27.5.2.4.2, который приводит нас к 27.7.1.3, чтобы определить, как работает underflow () (таким образом, объясняя, что буфер будет использоваться в качестве источника), а также 27.8.1.4, чтобы показать, что showmany () будет использоваться для определения того, будет ли использоваться буфер может быть пополнен из источника потока (или нет).
@Martin: Я думаю, что ваша ошибка в том, что вы думаете, что setbuf должен устанавливать указатели буфера (eback, gptr и т. д.). Но в стандарте этого не сказано, поэтому переполнение не связано. Давайте обсудим это здесь: stackoverflow.com/questions/4349778/…
Однако было бы относительно тривиально написать класс, производный от streambuf, который действительно использует предоставленный буфер.
@Ben Vogit: Мы выделили это в отдельный вопрос: Эффект основного потока
у меня не работает
Вам нравится копировать данные. 1) Скопируйте в буфер. 2) Скопируйте в анонимный std :: string. 3) Скопируйте в iss.