У меня есть приложение, которое читает из текстового файла, который одновременно записывается другим приложением. Приложение, которое читает файл, открывает его в текстовом режиме, используя C — fopen. Строки в файле могут быть большими, размером до нескольких сотен мегабайт. По этой причине у нас есть функция, которая читает фрагменты 4K из файла, используя fgets, и добавляет его в строковый объект, пока ему не удастся прочитать всю строку. Возможно, что приложение, которое записывает в файл, записало частичную строку, когда была сделана попытка чтения. Наша пользовательская функция ReadLine обрабатывает этот сценарий, обнаруживая конец файла, сбрасывая указатель файла на последнюю известную правильную позицию и отбрасывая текст, который уже был прочитан.
Вот как выглядит эта функция:
#define MAX_BUF_SIZE 4096
bool ReadLine(FILE* fp, std::string& result, bool& isEof) {
result.clear();
long const lastOffset = ftell(fp);
bool hasReadOneLine = false;
isEof = false;
debug_print(lastOffset);
while (!hasReadOneLine && !isEof) {
char dataRead[MAX_BUF_SIZE];
memset(dataRead, 0, sizeof(dataRead));
if (fgets(dataRead, MAX_BUF_SIZE, fp) == NULL) {
if (feof(fp)) {
debug_print("Flag 1");
isEof = true;
} else {
debug_print("Flag 2");
result.clear();
fseek(fp, lastOffset, SEEK_SET); //reset the file pointer to where it was
return false;
}
}
result += dataRead;
hasReadOneLine = (result[result.length()-1] == '\n');
} // end loop
if (!hasReadOneLine) {
debug_print("Flag 3");
result.clear();
fseek(fp, lastOffset, SEEK_SET); //reset the file pointer to where it was
return false;
}
// drop the new-line character ...
if (result[ result.length()-1] == '\n') {
result.resize(result.size() - 1);
}
return true;
}
Проблема:
Я столкнулся со сценарием, в котором после чтения полной строки из файла функция ReadLine () возвращает последний кусок из ранее прочитанной строки, когда она вызывается снова для чтения следующей строки. Я зарегистрировал значение lastOffset, возвращаемое функцией ftell (), и заметил, что в этом редком сценарии fgets не перемещает указатель файла в конец строки, которую он прочитал.
Я добавил несколько строк отладки, но в моем случае единственной вещью, которая была напечатана, было значение lastOffset.
В вызове, когда ReadLine возвратил неполную строку, значение lastOffset равно: 21563617
Длина неполной строки: 920
В вызове до того, где он возвратил полную строку, значение lastOffset равно: 21442207
Длина строки, прочитанной в этом предыдущем вызове: 122331 (включая новую строку)
Мой вопрос: кто-нибудь сталкивался с подобной проблемой? Что вы думаете о том, что может пойти не так? Я не обязательно ищу полный ответ, но просто несколько советов о том, что может пойти не так.
** ОБНОВИТЬ **
Мне удалось воспроизвести проблему с помощью небольшой утилиты, записавшей файл кусками по 4 Кб с интервалом ожидания 10 мс, в то время как другая программа (которая использует вышеописанную функцию) считывает данные из одного и того же файла одновременно.
Похоже, что выполнение fseek () для переустановки указателя файла — плохая опция в вышеприведенной функции, так как переустановка указателя файла в предыдущую позицию не обязательно очищает собственный внутренний буфер библиотеки C. Я до сих пор не полностью убежден в этом объяснении, так как в некоторых случаях (в случаях репро) сброса указателя файла никогда не происходило.
Во всяком случае, я провел еще несколько поисков в Интернете, и некоторые потоки, казалось, предлагали использовать потоки более низкого уровня и обрабатывать буферизацию в самой библиотеке. Поэтому я изменил реализацию функции выше и других ее помощников, чтобы сделать именно это. Теперь я использую _sopen_s () / _ read () / _ lseek () для Windows и стандартный интерфейс POSIX для Linux / Solaris для выполнения низкоуровневой обработки ввода-вывода. С этими изменениями это похоже на работу, я больше не вижу проблемы.
Спасибо всем за ваше время. Весь ваш вклад очень ценится.
Суман
** ОБНОВЛЕНИЕ 2 **
Ну, теперь я точно знаю причину. Проблема в том, что ftell () и fseek () ненадежны, если файл открывается в текстовом режиме. Если файл открывается в двоичном режиме, то вышеуказанная функция работает нормально.
Вот ссылка на статью, в которой кто-то другой уже сталкивался с этой проблемой: http://arstechnica.com/civis/viewtopic.php?f=20&т = 420490
Это хорошо, потому что теперь у меня есть исправление, которое требует изменения 1 строки вместо 200! 🙂
Если ваш максимальный размер строки меньше MAX_BUF_SIZE
Тогда вы можете рассмотреть альтернативное решение, которое значительно упростит вашу реализацию. Короче говоря, используйте fread
вместо fgets
:
void ReadLine(FILE* fp, std::string& result, bool& isEof)
{
static char dataRead[MAX_BUF_SIZE] = {0};
static int dataindex = 0;
int datalength = fread(dataRead,MAX_BUF_SIZE-dataindex,1,fp);
for (int i=0; dataRead[i]!='\n'; i++)
result += dataRead[i];
dataindex = result.length()+1;
memmove(dataRead,dataRead+dataindex,datalength-dataindex);
isEof = feof(fp);
}
Заметки:
Эта реализация предполагает, что последняя строка (отсюда и сам файл) заканчивается символом новой строки.
Ты можешь использовать dataRead
/dataindex
в качестве циклического буфера, чтобы избежать memmove
операция.
Других решений пока нет …