Книга: Системное программирование в среде Windows

Пример: обновление записей, находящихся в произвольном месте файла

Пример: обновление записей, находящихся в произвольном месте файла

Программа RecordAccess (программа 3.1) обеспечивает поддержку файлов фиксированного размера, состоящих из записей фиксированного размера. В заголовке файла хранится количество непустых записей, содержащихся в файле, а также емкость файла. Пользователю предоставляется возможность выполнять в интерактивном режиме чтение, запись (обновление) и удаление записей, каждая из которых содержит метки времени, текстовую строку и счетчик, показывающий, сколько раз запись изменялась. В качестве несложного и реалистичного расширения возможностей программы можно было бы добавить в структуру записи ключ и определять местоположение записей в файле путем применения хэш-функции к значениям ключа.

Программа демонстрирует позиционирование указателя файла перед заданной записью, а также выполнение 64-битовых арифметических операций с использованием данных типа LARGE_INTEGER Microsoft С. Чтобы проиллюстрировать логику работы указателей файла, в программу включен код, проверяющий наличие ошибок. Программа в целом иллюстрирует применение файловых указателей и множественных структур OVERLAPPED, а также обновление файлов с использованием 64-битовых файловых указателей.

Общее количество записей в файле указывается в командной строке; при большом количестве записей размеры создаваемых файлов могут быть очень большими и даже гигантскими, поскольку длина одной записи составляет примерно 300 байт. После выполнения нескольких экспериментов вы убедитесь, что большие файлы должны быть разреженным; в противном случае необходимо размещать и инициализировать на диске весь файл целиком, в результате чего может существенно увеличиться время обработки файла и занимаемое им место на диске. Хотя в листинге программы 3.1 это и не отражено, в программе предусмотрен участок кода, обеспечивающий создание разреженных файлов, если в этом возникает необходимость; в некоторых системах, например Windows XP Home, этот код правильно работать не сможет.

На Web-сайте книги предоставляются три дополнительные программы, родственные этой: tail.с — другой пример реализации произвольного доступа к файлу, getn.c — упрощенная версия программы RecordAccess, обеспечивающая лишь чтение записей, и atouMT (включена в программы для главы 14, находящиеся на Web-сайте, однако не включена в программы, приведенные в книге), также иллюстрирующая прямой доступ к файлам.

Программа 3.1. RecordAccess 

/* Глава 3. RecordAccess. */
/* Использование: RecordAccess имя файла [количество записей]
Количество записей (nrec) можно не указывать, если файл с указанным именем уже существует. Если количество записей (nrec) задано, создается файл с указанным именем (если файл с таким именем существует, он уничтожается). При большом количестве записей (nrec) файлы рекомендуется создавать как разреженные. */
/* Программа иллюстрирует:
1. Произвольный доступ к файлам.
2. Арифметику данных типа LARGE_INTEGER и использование 64-битовых указателей файла.
3. Обновление записей на месте.
4. Запись в файл нулей во время инициализации (требует использования файловой системы NTFS).
*/
#include "EvryThng.h"
#define STRING_SIZE 256
typedef struct _RECORD { /* Структура записи в файле */
 DWORD ReferenceCount; /* 0 означает пустую запись. */
 SYSTEMTIME RecordCreationTime;
 SYSTEMTIME RecordLastReferenceTime;
 SYSTEMTIME RecordUpdateTime;
 TCHAR DataString[STRING_SIZE];
} RECORD;
typedef struct _HEADER { /* Дескриптор заголовка файла */
 DWORD NumRecords;
 DWORD NumNonEmptyRecords;
} HEADER;
int _tmain(int argc, LPTSTR argv[]) {
 HANDLE hFile;
 LARGE_INTEGER CurPtr;
 DWORD FPos, OpenOption, nXfer, RecNo;
 RECORD Record;
 TCHAR String[STRING_SIZE], Command, Extra;
 OVERLAPPED ov = {0, 0, 0, 0, NULL}, ovZero = {0, 0, 0, 0, NULL};
 HEADER Header = {0, 0};
 SYSTEMTIME CurrentTime;
 BOOLEAN HeaderChange, RecordChange;
 OpenOption = (argc == 2) ? OPEN_EXISTING : CREATE_ALWAYS;
 hFile = CreateFile(argv[1], GENERIC_READ | GENERIC_WRITE, 0, NULL, OpenOption, FILE_ATTRIBUTE_NORMAL, NULL);
 if (argc >= 3) { /* Записать заголовок и заранее установить размер нового файла */
  Header.NumRecords = atoi(argv[2]);
  WriteFile(hFile, &Header, sizeof(Header), &nXfer, &ovZero);
  CurPtr.QuadPart = sizeof(RECORD)*atoi(argv[2])+sizeof(HEADER);
  FPos = SetFilePointer(hFile, CurPtr.LowPart, &CurPtr.HighPart, FILE_BEGIN);
  if (FPos == 0xFFFFFFFF && GetLastError() != NO_ERROR) ReportError(_T("Ошибка указателя."), 4, TRUE);
  SetEndOfFile(hFile);
 }
 /* Считать заголовок файла: определить количество записей и количество непустых записей. */
 ReadFile(hFile, &Header, sizeof(HEADER), &nXfer, &ovZero);
 /* Предложить пользователю считать или записать запись с определенным номером. */
 while(TRUE) {
  HeaderChange = FALSE;
  RecordChange = FALSE;
  _tprintf(_Т("Введите r(ead)/w(rite)/d(elete)/q Запись#n"));
  _tscanf(_T("%c" "%d" "%c"), &Command, &RecNo, &Extra );
  if (Command == 'q') break;
  CurPtr.QuadPart = RecNo * sizeof(RECORD) + sizeof(HEADER);
  ov.Offset = CurPtr.LowPart;
  ov.OffsetHigh = CurPtr.HighPart;
  ReadFile(hFile, &Record, sizeof(RECORD), &nXfer, &ov);
  GetSystemTime(&CurrentTime); /* Обновить поля даты и времени в записи. */
  Record.RecordLastRefernceTime = CurrentTime;
  if (Command == 'r' || Command == 'd') { /*Вывести содержимое записи.*/
   if (Record.ReferenceCount == 0) {
    _tprintf(_T("Запись номер %d – пустая.n"), RecNo);
    continue;
   } else {
    _tprintf(_Т("Запись номер %d. Значение счетчика: %d n"), RecNo, Record.ReferenceCount);
    _tprintf(_Т("Данные: %sn"), Record.DataString);
    /* Упражнение: вывести метки времени. См. следующий пример. */
    RecordChange = TRUE;
   }
   if (Command == 'd') { /* Удалить запись. */
    Record.ReferenceCount = 0;
    Header.NumNonEmptyRecords--;
    HeaderChange = TRUE;
    RecordChange = TRUE;
   }
  } else if (Command == 'w') { /* Записать данные. Впервые? */
   _tprintf(_Т("Введите новую строку для записи.n"));
   _getts(String);
   if (Record.ReferenceCount == 0) {
    Record.RecordCreationTime = CurrentTime;
    Header.NumNonEmptyRecords++;
    HeaderChange = TRUE;
   }
   Record.RecordUpdateTime = CurrentTime;
   Record.ReferenceCount++;
   _tcsncpy(Record.DataString, String, STRING_SIZE-1);
   RecordChange = TRUE;
  } else {
   _tprintf(_T("Допустимые команды: r, w и d. Повторите ввод.n"));
  }
  /* Обновить запись на месте, если ее содержимое изменилось. */
  if (RecordChange) WriteFile(hFile, &Record, sizeof(RECORD), &nXfer, &ov);
  /* При необходимости обновить количество непустых записей. */
  if (HeaderChange) WriteFile(hFile, &Header, sizeof(Header), &nXfer, &ovZero);
 }
 _tprintf(_T("Вычисленное количество непустых записей: %dn"), Header.NumNonEmptyRecords);
 CloseHandle(hFile);
 return 0;
}
 

Оглавление книги


Генерация: 3.028. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз