Джонсон Харт - Системное программирование в среде Windows
В случае разреженных файлов (sparse files), появившихся в Windows 2000, дисковое пространство расходуется лишь по мере записи данных. Администратор может назначать, какие файлы, каталоги или тома должны быть разреженными. Кроме того, можно назначить существующий файл в качестве разреженного с помощью функции DeviceIoControl, если установить при ее вызове флаг FSCTL_SET_SPARSE. Ситуацию, в которой удобно использовать разреженные файлы, иллюстрирует программа 3.1. К разреженным файлам функция SetValidFileData неприменима.
Файлы FAT нулями автоматически не инициализируются. Согласно документации Microsoft содержимое вновь созданных файлов не определено, что подтверждается экспериментами. Поэтому, если для корректной работы требуется инициализация файлов, приложения должны это делать самостоятельно путем вызова функции WriteFile. Файлы NTFS будут инициализированы, поскольку уровень безопасности С2, обеспечиваемый Windows, требует, чтобы чтение содержимого удаленных файлов было невозможным.
Обратите внимание, что кроме функции SetEndOfFile существуют и другие способы расширения размера файла. Так, можно расширить файл, используя ряд последовательных операций записи, хотя при этом существует риск увеличения степени фрагментации файла; размещение на диске файлов в виде непрерывных блоков большого размера функция SetEndOfFile отдает на откуп операционной системе.
Пример: обновление записей, находящихся в произвольном месте файла
Программа 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;
}
Атрибуты файлов и управление каталогами
Существует возможность просмотра указанного каталога с целью поиска файлов и других каталогов, имена которых соответствуют заданному шаблону, одновременно с получением атрибутов файлов. Для выполнения поиска требуется дескриптор поиска (search handle), получаемый с помощью функции FindFirstFile. Для нахождения файлов, имена которых удовлетворяют заданным условиям, используется функция FindNextFile, а для прекращения поиска — функция FindClose.