Страницы

Поиск по вопросам

суббота, 28 декабря 2019 г.

Запись текстовой строки по нужному индексу в файл на языке Си?

#c


Здравствуйте. Интересует вопрос, как можно записать в файл строку, не трогая остальные
строки по нужному индексу.

Например, у меня 3 строки в файле:

строка 1
строка 2
строка 3


Мне нужно заменить строку 2 на ййййй:

строка 1
ййййй
строка 3


То что получается у меня, это постоянная перезапись всего файла с помощью fprintf().
Но так не пойдет, у меня там 100 000 записей. Также нужно вставлять строки небольшими
блоками (примерно по 60 строк).

Заранее всем спасибо. Использовать можно только функции языка Си.
    


Ответы

Ответ 1



Нежозможно заменить блок в середине файла на блок другого размера, при этом не разрушив последующего содержимого файла. Другими словами - не системном уровне обычно невозможно вставить новое и сместить старое содержимое файла. Если kernel и filesystem поддерживают, то есть очень ограниченная возможность писать в середине файла. Самое оптимальное решение - открыть оригинал для чтения, читать построчно и добавляя/изменяя писать в новый файл, в конце заменив оригинал новым. Можно конечно попробовать найти место откуда писать, и начинать оттуда переписывать, т.е. seek(), fread(), seek() и fwrite(), но весь остаток предыдущего содержимого файла должен быть в памяти, и если fwrite() по какой-либо причине не завершится, оригинальный файл будет утерян. Если он дорог как память и надо избежать случая - батарейка села - содержимое памяти надо дублировать в swap с возможностью recovery. Так что или решение описанное выше, или меняйте формат, или сторонние решения / библиотеки - например Berkeley DB.

Ответ 2



Как вы понимаете, если вставляемая строка не равна по длине той, которую она заменяет - счастья (вставить только ее, не трогая остальные) не получится. Если вам повезло и она точно такая же по длине - становитесь на нужное место в том же файле с помощью функции fseek и записываете строку с помощью fwrite или той же fprintf. Если не повезло - то надо переписывать данные в другой файл (так проще) с последующим его переименованием. Сами прикиньте, какая морока, если вставляемая строка длиннее, чем та, что была - надо постоянно считывать данные наперед, чтоб не затерлись при перезаписи, и хранить их... Перезаписывать в другой файл большими блоками просто - выделяете буфер, открываете файлы (исходный и вновь созданный) в бинарном режиме (иначе считать будет трудно), и читаете-пишете блоками с помощью fread и fwrite. Аккуратно отслеживая, сколько именно надо прочитать до искомой строки, затем новую строку, затем переход с помощью fseek за строку, и снова читаете-пишете до конца файла. Естественно, сначала вам нужно найти, с какой позиции в файле располагается искомая строка. Если файл небольшой и легко размещается в памяти - проще считать его весь в буфер достаточного размера, с помощью memmove расчистить место под запись строки, обнулить старый файл и записать заново. Если эта операция у вас постоянная - крайне рекомендую подумать об использовании другого формата файла. Например, при известной максимальной длине строки хранить каждую строку в блоке размера, достаточного для размещения максимальной строки. Вы потеряете на размере файла, но очень сэкономите на файловых операциях. Есть и другие варианты, но этот самый простой.

Ответ 3



Первое, что вам нужно сделать, это определить максимальную возможную длину строки, которую вы планируете писать в файл. Затем, представьте файл, как массив строк этой фиксированной длины. И теперь, поскольку длина фиксирована, найти позицию в файле, куда нужно записывать данные, не составит труда. Главное, не забыть пустое место в конце строк, меньших максимальной, забить пробелами, чтобы в файл не записался случайный мусор. При таком подходе у вас будет оверхед на занимаемое место на диске, но вы получите возможность перезаписывать любую строку в любой момент времени. Если вас такой оверхед не устраивает, то вам нужно смотреть в сторону БД (SQLite) поскольку руками сделать нормальное изменение произвольных (не фиксированных по длине) строк невозможно. Хотя, в БД тоже будет небольшой оверхед по диску, так что тут надо смотреть на конкретных данных и принципиальной возможности ввести/выбрать фиксированную длину строки.

Ответ 4



Если отвечать формально, то - никак. Если же попытаться понять, что нужно автору на самом деле - то становится ясно, что задача - типовая и решение у неё тоже - типовое. Первое, что нужно сделать - отвязать физическое расположение строки (записи) в файле от её логического порядка (номера, индекса ...) В файл физического хранения строк сами строки пишем как попало. Можно даже не писать символы перевода строки. В 99.99% случаев новую строку пишем просто в конец этого файла. Заводим хэш-таблицу (для лубителей STL - map), в которой ключём будет ЛОГИЧЕСКИЙ номер строки, а значением - струкутра вида: struct Descriptor { size_t pisition; size_t len; } Если объём таблицы не слишком велик, то можно её целиком в памяти разместить, в противном случае, проще всего воспользоваться Berkeley DB для хранения этой таблицы в виде файла. Т.е., зная ЛОГИЧЕСКИЙ номер строки, Вы считываете её дескриптор из хэш-таблицы, а по нему считываете саму строку из основного файла. Таким образом, для выбора строки нужно всего два (!) обращения к диску. Для добавления новой строки нужно записать её в конец основного файла и занести её координаты в хэш. Т.е. тоже два обращения. С удалением строк из основного файла несколько сложнее получается. Нужно написать специальный сборщик мусора и запускатье его, когда нет загрузки основной системы.

Ответ 5



Если вы хотите добавить новые строки в середину текстового файла, то вам придётся переписать все 100_000 строк, чтобы добавить только одну строчку в середину (пишите в новый файл новое содержимое, переименовывайте в случае успеха). Если вас это не устраивает, то используйте другой формат, например, работайте с файлом как с базой данных, используя sqlite библиотеку. База не подходит по ТЗ должно быть именно так. Пусть длина строки фиксированная Если вам нужно только отредактировать строки, не меняя их длину, то можно fseek()/fwrite() использовать, чтобы отредактировать строку с индексом line_index: #include /** Replace line at *line_index* with *new_line* in the *file*. All lines in the *file* are *line_size* size. Return non-zero on error (*errno* may be set). */ int freplace(size_t line_index, char* new_line, size_t line_size, FILE* file) { return (fseek(file, line_index * line_size, SEEK_SET) || fwrite(new_line, line_size, 1, file) != 1); } Например, чтобы заменить вторую строчку (индекс 1) на abc в файле lines.txt, где все строчки имеют длину 4 (включая перевод строки): #include #include int main(void) { FILE* file = fopen("lines.txt", "r+b"); if (file == NULL || freplace(1, "abc\n", 4, file) || fclose(file)) { perror("failed to replace the line"); exit(EXIT_FAILURE); } return 0; } Если lines.txt: aaa bbb ccc то после запуска программы, содержимое превращается в: aaa abc ccc

Комментариев нет:

Отправить комментарий