Работа с бинарными файлами на Си
Задание такое: есть бинарный файл, содержащий целые положительные и отрицательные числа. Из него нужно удалить самое большое и самое маленькое. Я смог найти эти числа и добавить в файл, но придумать, как их, наоборот, удалить, у меня не получается. Пожалуйста, помогите
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <limits.h>
/* Изменение файла в соответствии с заданием */
void edit_file(char *filename);
/* вывод содержимого бинарного файла на экран */
void out_file(char *filename);
int main(int n_arg, char *arg[])
{
system("chcp 65001"); // смена кодировки, если файл в кодировке utf-8
system("cls");
char *file_name;
FILE *fin, *fout;
//Чтение аргументов программы
if (n_arg > 1){
file_name = (char*)malloc(sizeof(arg[1]));
file_name = arg[1];
} else {
file_name = (char*)malloc(sizeof(char)*20);
//Если аргументы не заданы ввод названия файла пользователем.
//Тут ограничение на название файла 20 символов.
printf("Введите имя файла: ");
if (!fgets(file_name, 20, stdin)){
printf("\nИмя файла введено неверно");
return 1;
};
file_name[strlen(file_name)-1] = '\0';
}
printf("Исходный файл:\n");
out_file(file_name);
edit_file(file_name);
printf("Измененный файл:\n");
out_file(file_name);
printf("\n");
system("pause");
return 0;
}
void out_file(char *filename)
{
// вывод содержимого файла на экран
int n = 0;
FILE *fin;
fin = fopen(filename, "rb");
if(!fin)
{
printf("Невозможно открыть бинарный файл\n");
system("pause");
exit(0);
}
while(fread(&n, sizeof(n), 1, fin))
{
printf("%5d", n);
}
fclose(fin);
}
void edit_file(char *filename)
{
// изменение содержимого файла
int n = 0, max=INT_MIN, min=INT_MAX;
FILE *fin;
fin = fopen(filename, "r+b");
if(!fin)
{
printf("Невозможно открыть бинарный файл\n");
system("pause");
exit(0);
}
rewind(fin);
//сдвигаем все элементы на 1
while(fread(&n, sizeof(n), 1, fin))
{
if (n > max) max=n; //проверяем максимум
if (n < min) min=n; //проверяем минимум
}
//записываем максимум
fwrite(&max, sizeof(n), 1, fin);
//записываем минимум
fwrite(&min, sizeof(n), 1, fin);
fclose(fin);
printf("\n\nФайл изменен\n");
}
Ответы (1 шт):
Функции работы с файлами не предусматривают "удаление" куска. Файл представлен как "неразрывная лента". В ленту можно писать, можно перемещаться по массиву влево вправо. Можно делать его короче. С файлом нужно работать где-то так же как и с одним обычным массивом типа char array[] без дополнительных удобств, только длинну можно менять.
Так как явно вырезать кусок файла нельзя, то делается "косвенное" вырезание. Для файла применимы следующие операции. Open открытие. Close - закрытие. Seek - перемещение. Read чтение. Write - запись. Trunc - обрезание. Я бы выделил три алгоритма перезаписи файла.
Trunc в разных системах запысывается по-разному. Я использовал _chsize из io.h. Но что бы она работала, пришлось взять файловый дескриптор используя fileno, и текущую позицию файла, используя ftell. Итого получилось _chsize(fileno(f),ftell(f));. В WINAPI можно использовать SetEndOfFile, у unix ф-ция ftruncate cкорее всего.
Простейший алгоритм будет такой: Open, Read, обработка, Seek(0), Write(весь файл), Truncate. Т.е.
#include <io.h> //... FILE *fin; fin = fopen(filename, "r+b"); // .... fseek(fin,0,0); fwrite(новый файл); // Записываем весь буфер с новыми данными _chsize(fileno(f),ftell(f)); // truncate установим размер файла fclose(f);
при записи, не обязательно записывать всё одним куском. Вы можете поставить цикл, и записать все элементы кроме удалённых последовательно.
Что бы не лопатить весь файл, можно например удалить строку 2 из файла. Создаём файл, записываем в него 1 2 3 4 через возврат каретки. Удалить строку номер 2 можно так (код не идеальный, для примера как выкинуть кусок файла с from по to)
#include <stdio.h> #include <io.h> int main(int n_arg, char *arg[]) { FILE* f = fopen("1.txt","r+b"); // Нет проверки char myfile[4096]; int size = fread(myfile,1,4096,f); // Читаем весь файл int from=0, to=0, i; // Поиск первого символа 13 for (i=0;i<size;i++) if (myfile[i]==13) { from = ++i; break; } // Поиск второго символа 13 for (i=from;i<size;i++) if (myfile[i]==13) { to = ++i; break; } if ((from !=0) && (to !=0)) { fseek(f,from,0); // Оставляем начало файла по from без изменений fwrite(myfile + to,size - to, 1, f); // Записывам изменившуюся часть файла _chsize(fileno(f),ftell(f)); // Обрезаем старый "хвост" } fclose(f);Если мы не хотим читать весь файл в память, прийдётся брать буфер, и "жанглировать" файлом, делая операции seek-read-обработка-seek-write. Таким образом можно "перемещать" куски файла с место на место. Что то вроде memcpy...
bool MoveInFile(FILE * f, long from, long to, int size) { char buffer[256]; // Или задать нужный размер отличный от 256, или делать динамический буфер if (size > sizeof(buffer)) return false; fseek(f,from,0); fread(buffer,1,size); // Прочли кусок в буфер fseek(f,to,0); fwrite(buffer,1,size); // Записали прочитаный кусок в другую позицию return true; }
Алгоритм сложный - приводить полностью не буду. Тем более что при значительно больших кусках эту ф-цию прийдётся сильно усложнить - разбить один MoveInFile на несколько таких, что бы каждый кусок поместился в буфер. Это делают в крайних случаях.
Но это не всё.
- Нужно помнить, что такие операции пригодны при размере файла где-то до 10...100 МБайт, и то при размере выше 1Мб будут проблемы. Если куски мелкие - могут быть проблемы с быстродействием. Так же может возникнуть проблема - где и как получить нужное колличество ОЗУ. В x86 приёдется брать память мелкими кусками. При работе с большими файлами - нужно писать кусками не менее 4096 байт, желательно кусками не меньше чем размер кластера файловой системы, и желательно выравнивать обращения по этим границам. Если раньше 4096 было стандартно, думаю скоро перейдём на 8192 байта. Поэтому при работе с файлами более 1 Мб часто используют классы/библиотеки кеширования.
- При работе с файлами свыше 4Гб прийдётся использовать seek64 или что-то подобное.
- Желательно учитывать littel-indian/big-indian иначе можно поймать UB.
- Иногда желательно делать fflush перед закрытием, или в случае если вы делаете логирование.
P.S. Ваш алгоритм не подходит для работы с файлами соовсем. Потому что нужно заранее знать какой вид будет иметь выходной файл. Теоретически можно вычислить позицию первого и второго удаляемого куска, но, т.к. вы не храние файл в памяти - можно сделать перемещение по алгоритму 3 через MoveInFile. Но это будет сильно сложно для вас, но и к тому же сильно нерационально, т.к. ради одного-двух удалений прийдётся сделать несколько перезаписей файла. Этот алгоритм выгодно использовать только если соовсем не хватает памяти для метода 1,2 и файл более 1Мб (на глаз). Я думаю лучше вам лучше вычитать всё, и сделать по алгоритму 1.