Как прочитать построчно текстовый файл на C? fgets не устраивает тем, что нельзя определить, был ли достигнут конец строки, если строка не поместилась в буфер целиком, либо нужно каждый раз определять длину строки, чтобы проверить, является ли последний прочитанный символ символом \n, что существенно увеличивает накладные расходы (каждый раз после чтения вызывать strlen(), для моей задачи это критично). Есть ли аналогичная fgets() функция, но работающая по принципу "все или ничего"?
Здравствуйте, Аноним, Вы писали:
А>Как прочитать построчно текстовый файл на C? fgets не устраивает тем, что нельзя определить, был ли достигнут конец строки, если строка не поместилась в буфер целиком, либо нужно каждый раз определять длину строки, чтобы проверить, является ли последний прочитанный символ символом \n, что существенно увеличивает накладные расходы (каждый раз после чтения вызывать strlen(), для моей задачи это критично). Есть ли аналогичная fgets() функция, но работающая по принципу "все или ничего"?
Здравствуйте, Аноним, Вы писали:
А>Как прочитать построчно текстовый файл на C? fgets не устраивает тем, что нельзя определить, был ли достигнут конец строки, если строка не поместилась в буфер целиком, либо нужно каждый раз определять длину строки, чтобы проверить, является ли последний прочитанный символ символом \n, что существенно увеличивает накладные расходы (каждый раз после чтения вызывать strlen(), для моей задачи это критично). Есть ли аналогичная fgets() функция, но работающая по принципу "все или ничего"?
В С нет.
Подозреваю, все делают следующим способом:
1. Поблочная вычитка файла
2. Как только в блоке натыкаемся на '\n', считаем длину получившейся строки, выделяем буфер и сливаем туда вычитанные блоки, включая кусок из последнего до '\n'.
3. Вытираем из буфера все обработанные блоки, оставляя только "хвост" последнего
4. Переходим к п. 1
Здравствуйте, Аноним, Вы писали:
А>Как прочитать построчно текстовый файл на C? fgets не устраивает тем, что нельзя определить, был ли достигнут конец строки, если строка не поместилась в буфер целиком, либо нужно каждый раз определять длину строки, чтобы проверить, является ли последний прочитанный символ символом \n, что существенно увеличивает накладные расходы (каждый раз после чтения вызывать strlen(), для моей задачи это критично). Есть ли аналогичная fgets() функция, но работающая по принципу "все или ничего"?
Нафига strlen?! Вообще-то пишется сходу. Задачка уровня тривиального собеседования.
типа файл fgets.c
#include <stdio.h>
#include <stdlib.h>
char *safe_fgets(FILE *f)
{
enum { step = 5 }; /* 128? 256? ... set your own step here */int R = step;
int L = 0;
char *s = malloc(step+1);
s[0] = 0;
s[R-1] = 0;
for ( ;; )
{
if ( !fgets(s+L,R-L+1,f) )
if ( feof(f) && s[0] )
return s;
else
{ free(s); return 0; }
if ( *(s+R-1) && *(s+R-1) != '\n' )
{
s = realloc(s,R+step+1);
L = R;
R = R+step;
s[R-1] = 0;
}
else
return s;
}
}
int main()
{
FILE *f = fopen("fgets.c","r");
char *s;
while ( 0 != (s = safe_fgets(f)) )
{
fputs(s,stderr);
free(s);
}
fclose(f);
return 0;
}
Здравствуйте, igna, Вы писали:
AS>>типа файл fgets.c I>Мой вариант:
Тоже вариант. Но два момента: первый — как уже сказали выше, не всегда можно позиционироваться (например чтение с TCP сокета хидеров в HTTP-ответе); второй — чтение файла медленнее чем выделение памяти.
Здравствуйте, Alexéy Sudáchen, Вы писали:
AS>Тоже вариант. Но два момента: первый — как уже сказали выше, не всегда можно позиционироваться (например чтение с TCP сокета хидеров в HTTP-ответе); второй — чтение файла медленнее чем выделение памяти.
Позиционироваться можно действительно не всегда, что есть — то есть. Но насчет медленнее — в общем случае не согласен, в случае нормальных файлов под Windows повторного чтения с диска не будет, второй раз чтение будет из кэша.
Здравствуйте, Alexéy Sudáchen, Вы писали:
AS>Здравствуйте, Аноним, Вы писали:
А>>Как прочитать построчно текстовый файл на C? fgets не устраивает тем, что нельзя определить, был ли достигнут конец строки, если строка не поместилась в буфер целиком, либо нужно каждый раз определять длину строки, чтобы проверить, является ли последний прочитанный символ символом \n, что существенно увеличивает накладные расходы (каждый раз после чтения вызывать strlen(), для моей задачи это критично). Есть ли аналогичная fgets() функция, но работающая по принципу "все или ничего"?
AS>Нафига strlen?! Вообще-то пишется сходу. Задачка уровня тривиального собеседования.
Этот подход никуда не годится. Выделение памяти внутри fgets исключает возможность работы со статическим буфером и проверку его размера — то есть семантика вашей safe_fgets отличается от семантики fgets, а имя вводит в заблуждение. И, кстати, пользователю всё равно придётся в конце вызывать strlen чтобы определить сколько же вы ему вернули. Два байта? Килобайт? Четыре мегабайта? Это кстати даёт свободу некоторым интересным сценариям (особенно при сетевой работе приложения, так что зря вы употребили там слово "safe").
Как я вижу основную проблему: чтобы сохранить семантику fgets нужно либо как igna сделать необоснованное предположение что мы можем позиционировать поток (что не так для исходной fgets => снова не решает задачу, а лишь маскирует уход от ответа) либо хранить где-то "хвосты" считанных и не понадобившихся буферов. Но в этом случае у нас во-первых требуется теоретически очень большое место в памяти, во-вторых мы должны как-то узнавать потоки, чтобы знать кому какой из сохранённых хвостов пришивать, а FILE*, как я понимаю не даёт, нам такой гарантии.
Что в общем случае наводит на мысль что задача не решаема -- пользовательской программе всё равно придётся предпринимать некоторые нетривиальные действия при работе с fgets. Либо вручную контролировать "позиционируемость потока", либо вручную хранить неиспользованные недосчитанные хвосты.
Здравствуйте, Tilir, Вы писали:
T>Этот подход никуда не годится. Выделение памяти внутри fgets исключает возможность работы со статическим буфером и проверку его размера — то есть семантика вашей safe_fgets отличается от семантики fgets, а имя
Где-то в исходном топике было про семантику fgets?! Назовите функцию fdghgdf1 — что теперь задача решена? Для семантики fgets — нужно пользовать fgets, и не сношать мозг.
T>Что в общем случае наводит на мысль что задача не решаема -- пользовательской программе всё равно придётся
Передать в функцию указатель на длину прочитанной строки руки отвалятся?
Здравствуйте, Alexéy Sudáchen, Вы писали: AS>Нафига strlen?! Вообще-то пишется сходу. Задачка уровня тривиального собеседования.
Скрытый текст
AS>
AS>#include <stdio.h>
AS>#include <stdlib.h>
AS>char *safe_fgets(FILE *f)
AS> {
AS> enum { step = 5 }; /* 128? 256? ... set your own step here */
AS> int R = step;
AS> int L = 0;
AS> char *s = malloc(step+1);
AS> s[0] = 0;
AS> s[R-1] = 0;
AS> for ( ;; )
AS> {
AS> if ( !fgets(s+L,R-L+1,f) )
AS> if ( feof(f) && s[0] )
AS> return s;
AS> else
AS> { free(s); return 0; }
AS> if ( *(s+R-1) && *(s+R-1) != '\n' )
AS> {
AS> s = realloc(s,R+step+1);
AS> L = R;
AS> R = R+step;
AS> s[R-1] = 0;
AS> }
AS> else
AS> return s;
AS> }
AS> }
AS>int main()
AS> {
AS> FILE *f = fopen("fgets.c","r");
AS> char *s;
AS> while ( 0 != (s = safe_fgets(f)) )
AS> {
AS> fputs(s,stderr);
AS> free(s);
AS> }
AS> fclose(f);
AS> return 0;
AS> }
AS>
Ну да, задачка уровня собеседования — проваленного собеседования.
За такой код на собеседовании сразу пинком под зад: он нечитаемый, неверифицируемый, опасный, переменные названы одной буквой. Решает элементарную задачу, но в нем сложно разобраться.
Здравствуйте, Kerbadun, Вы писали:
K>За такой код на собеседовании сразу пинком под зад: он нечитаемый, неверифицируемый, опасный, переменные названы одной буквой. Решает элементарную задачу, но в нем сложно разобраться.
Дык, напиши показательный пример — читаемый, верифицируемый, неопасный, с переменными названными длиннымПонятнымИменем. Ну и чтобы в нём было просто разабраться. Что бы это в твоём понимании не значило. В общем удовлетворяющий твоим критериям. Ты же аноним, чего те боятся то?
Про "Правильный Код" (tm) каждый дурак рассуждать может. Кстати, это не моя проблема, то что кто-то не умеет читать код, и для него r,l заместо right, left — непреодолимое препятствие. Как собственно и незнание устройства сишной строки.
Здравствуйте, Tilir, Вы писали:
T>Что в общем случае наводит на мысль что задача не решаема -- пользовательской программе всё равно придётся предпринимать некоторые нетривиальные действия при работе с fgets. Либо вручную контролировать "позиционируемость потока", либо вручную хранить неиспользованные недосчитанные хвосты.
В догонку. У топикстартера конкретная задача — прочитать файл построчно. Конкретная проблема, неизветсно какого размера нужен буффер, чтобы достоверно прочитать строку. Как результат нужно делать лишний strlen, именно для того, чтобы узнать прочитали всю строку или нет. Strlen там всё равно дальше нужно делать для обработки строки, и топикстартера это не смущает.
Собственно, задачу топикстартера мой код решает. То что он не решает сферическое в вакууме чтение из файла — дык, а зачем?! Он не для этого написан.
Здравствуйте, Аноним, Вы писали:
А>Как прочитать построчно текстовый файл на C? fgets не устраивает тем, что нельзя определить, был ли достигнут конец строки, если строка не поместилась в буфер целиком, либо нужно каждый раз определять длину строки, чтобы проверить, является ли последний прочитанный символ символом \n, что существенно увеличивает накладные расходы (каждый раз после чтения вызывать strlen(), для моей задачи это критично). Есть ли аналогичная fgets() функция, но работающая по принципу "все или ничего"?
Самое простое и элегантное решение (но при этом, увы, платформо-зависимое) для этой задачи заключается в том, чтобы файл не читать, буфера не выделять и не перевыделять и размеры их не рассчитывать. Все, что нужно, уже есть в файле, поэтому всего лишь надо спроецировать его в память, и задача практически решена!
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hFile = CreateFile("D:\\a.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
int sizeChars = GetFileSize(hFile, NULL);
HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
char* lpText = (char*) MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
char* lpCurrent = lpText;
while(lpCurrent - lpText < sizeChars )
{
char* lpLineStart = lpCurrent;
while (lpCurrent - lpText < sizeChars && *lpCurrent != 0xD) // первое условие увы, необходимо , так как в конце последней строки может не быть CR
lpCurrent++;
// lpLineStart - начало, (lpCurrent -1) - конец
// отладочная печатьfor(char* p = lpLineStart; p < lpCurrent; p++)
putchar(*p);
putchar('\n');
// конец отладочной печати
lpCurrent += 2;
}
UnmapViewOfFile(lpText);
CloseHandle(hFileMapping);
CloseHandle(hFile);
return 0;
}
Здравствуйте, Аноним, Вы писали:
А>Как прочитать построчно текстовый файл на C? fgets не устраивает тем, что нельзя определить, был ли достигнут конец строки, если строка не поместилась в буфер целиком, либо нужно каждый раз определять длину строки, чтобы проверить, является ли последний прочитанный символ символом \n, что существенно увеличивает накладные расходы (каждый раз после чтения вызывать strlen(), для моей задачи это критично). Есть ли аналогичная fgets() функция, но работающая по принципу "все или ничего"?
Ты тормозным fgets'ом читаешь текстовый файл и думаешь, что strlen что-то там замедлит?
Прежде чем делать такие серьезные выводы, рекомендую сначала померить профайлером, хотя бы.
На мой взгляд, strlen тормозить никак не должен, так как fgets должен несравнимо больше времени занимать.
Ты подумай: fgets же к диску обращается! Причем он внутри себя реализует некий не самый производительный алгоритм, который читает файл по строкам, хотя исходный файл-то бинарный.
По сравнению с этим одинокий strlen, один раз линейно сканирующий участок оперативной памяти, который, возможно, и так уже целиком сидит в кеше процессора — вообще ничто.
Вообще, читать файл fgets'ом — это далеко не лучшее решение в плане производительности.
Здравствуйте, Alexéy Sudáchen, Вы писали:
AS>Где-то в исходном топике было про семантику fgets?! Назовите функцию fdghgdf1 — что теперь задача решена? Для семантики fgets — нужно пользовать fgets, и не сношать мозг.
Прочитать файл построчно можно, кто бы спорил. Проблема поставленная топик-стартером сама по себе проста и неинтересна. Но он неявно сформулировал гораздо более интересную проблему -- разработать такую реализацию процедуры считывания строки, которая придерживалась бы семантики fgets, но работала по принципу "всё или ничего" -- то есть ничего не возвращала, если за заданную длину буфера конец строки не достигнут. Пока что лучшую попытку расколоть именно этот орешек сделал igna, но у него предполагается позиционирование итератора.
Здравствуйте, Tilir, Вы писали:
T>Прочитать файл построчно можно, кто бы спорил. Проблема поставленная топик-стартером сама по себе проста и неинтересна. Но он неявно сформулировал гораздо более интересную проблему -- разработать такую реализацию процедуры считывания строки, которая придерживалась бы семантики fgets, но работала по принципу "всё или ничего" -- то есть ничего не возвращала, если за заданную длину буфера конец строки не достигнут. Пока что лучшую попытку расколоть именно этот орешек сделал igna, но у него предполагается позиционирование итератора.
Ну для этого всего лишь нужен обЪект-буффер с динамическим размером. Делал я так — смысла нет. Ну как минимум для меня. С дескрипторными функциями смысл есть (и для них fgets надо самому писать), но там такой буффер и просто как буффер работает. На потоках же и так буфер уже есть.
Можно ещё с буфером потока помухлевать =) но это уже совсем чёрная магия.