Основы работы с файлами в C #
Работа с файлами в языке C # реализуется посредством потоков. Поток – это абстрактное понятие, описывающее любой перенос данных от источника к приемнику. Именно потоки позволяют считывать данные из файла и записывать данные в файл. Поток представляет собой последовательность байтов и не зависит от конкретного устройства, с которым производится обмен (оперативная память, файл на диске, клавиатура, принтер).
Для поддержки потоков библиотека .NET содержит иерархию классов, основная часть которой представлена на рис. 10.1.
Отметим, что эти классы определены в пространстве имен System.IO, поэтому чтобы воспользоваться объектами и методами этих классов, необходимо указать пространство имен System.IO в области директивы using в начале программы.
Рассмотрим некоторые из перечисленных выше классов, необходимые для организации работы с фалами, подробнее.
Классы TextReader и StreamReader
В языке C # такие операции как считывание данных из файла и запись данных в файл реализованы на основе манипуляций с байтами. Однако в связи с тем, что человеку гораздо более удобно воспринимать информацию, представленную в символьном формате, в библиотеке .NET разработаны классы StreamReader и StreamWriter, которые позволяют преобразовывать байтовые потоки в символьные и наоборот.
|
|
Как видно из рисунка 10.1,класс StreamReader является потомком абстрактного базового класса TextReader, предоставляющего классам-наследникам набор методов, реализующих считывание последовательности символов или строк из потока (файла). Некоторые из методов приведены в таблице 10.1.
Таблица 10.1
Некоторые методы класса TextReader
Название | Описание |
Peek () | Возвращает следующий символ из потока, при этом указатель текущей позиции не перемещается (удобно использовать для проверки наличия следующего символа в потоке: если конец файла достигнут, возвращаемое значение равно –1) |
Read () | Первый вариант: считывает текущий символ из потока и передвигает указатель на один символ в соответствии с кодировкой. Если доступных для считывания символов нет, возвращает значение –1. Второй вариант: считывает из потока последовательность символов и записывает ее в массив символов, начиная с определенного индекса. Метод возвращает фактическое количество считанных символов, либо нуль при отсутствии символов, доступных для чтения |
ReadBlock () | Блокирующая версия метода Read () |
ReadLine () | Считывает строку символов из текущего потока и возвращает данные в строковом формате либо значение null, если достигнут конец потока входных данных |
ReadToEnd () | Считывает все символы, начиная с текущей позиции, до конца потока как одну строку |
Поясним разницу между блокирующими и неблокирующими методами чтения. Обмен с потоком для повышения скорости передачи данных производится, как правило, через специальную область оперативной памяти, называемую буфер. Буфер выделяется для каждого открытого файла. При чтении из файла данные вначале считываются в буфер, причем не столько, сколько запрашивается, а сколько помещается в буфер, а уже из буфера передаются в поток. Таким образом, в процессе считывания, возможна ситуация, когда буфер пуст, т. е. данные пока не доступны, но ожидаемы в будущем. Именно в такой ситуации блокирующие и неблокирующие методы ведут себя по-разному: первые блокируют процесс считывания, помещая его в режим сна до тех пор, пока не появятся доступные для чтения данные, в то время как вторые просто возвращают в процесс некоторое значение, указывающее на то, что доступных для чтения данных нет.
|
|
Для того чтобы получить доступ к любому из описанных выше методов, необходимо создать экземпляр класса StreamReader с помощью конструктора StreamReader(<ИмяФайла>), при этом создается экземпляр класса StreamReader и связывается с конкретным физическим файлом. В качестве параметра можно указать либо только имя файла и в этом случае файл должен находиться в папке “…\ bin \ debug ” текущего проекта, либо полностью путь и имя файла. Пример использования:
try
{
StreamReader InputFile = new StreamReader("input.txt");
while (InputFile.Peek()!= -1)
Console.Write((char)InputFile.Read());
InputFile.Close();
}
catch
{
Console.WriteLine("Ошибка чтения файла");
}
Здесь мы создаем экземпляр класса StreamReader и связываем его с файлом “ input. txt ”. Далее в цикле while считываем посимвольно данные из файла с помощью метода Read () и выводим их на экран. Обратите внимание на то, что метод Read () возвращает значение считанного символа в формате int, поэтому мы преобразуем его в символьный формат. После того как конец файла достигнут, необходимо освободить ресурсы памяти, выделенные потоку, с помощью метода Close (). Оператор try позволяет обработать ошибку, которая может возникнуть при работе с файлом, например, в случае если файл не найден.
Напомним также о необходимости указать пространство имен System. IO в области директивы using в начале программы, иначе компилятор не сможет распознать встроенный класс StreamReader.
Для того чтобы проверить работоспособность программы, необходимо создать текстовый файл ” input. txt ” и сохранить его в папке “…\ bin \ debug ” текущего проекта. Это можно сделать в любом текстовом редакторе, но удобнее – непосредственно в среде Visual Studio. Для этого нужно выбрать пункт меню File – New – File …, в открывшемся диалоговом окне создания нового файла выбрать тип “ Text File ”, а далее ввести в файл данные и сохранить его.
Отметим, что альтернативой методу Close () может служить использование конструкции using, в этом случае занимаемая потоком память освобождается автоматически при выходе из конструкции. Пример:
try
{
using (StreamReader InputFile = new
StreamReader(@"c:\work\input.txt"))
while (InputFile.Peek()!= -1)
Console.WriteLine(InputFile.ReadLine());
}
catch
{
Console.WriteLine("Ошибка чтения файла");
}
Здесь мы считываем данные из файла построчно и выводим их на экран. Обратите внимание, что путь к файлу указан полностью, при этом символ ‘@’ позволяет блокировать управляющие символы.
Отдельно рассмотрим в качестве примера заполнение одномерного и двумерного массивов из файла.
В процессе заполнения одномерного массива из файла следует учитывать следующую особенность: фактическая размерность массива, т. е. количество элементов массива, записанных в файл, заранее неизвестно. Отсюда при инициализации массива мы указываем предельно допустимую размерность массива MaxArraySize, а фактическую размерность ArraySize определяем в процессе чтения из файла. Все остальные операции с массивом, такие как вывод или просмотр элементов массива, нужно выполнять, учитывая не максимально допустимую, а фактическую размерность массива.
|
|
Итак, опишем метод ReadFromFile (), реализующий заполнение целочисленного одномерного массива MyArray из заданного файла. Исходя из условия задачи, в качестве параметров данного метода введем, во-первых, имя файла FileName, во-вторых, одномерный массив MyArray, и, наконец, фактическую размерность массива ArraySize, определяемую в теле метода.
static void ReadFromFile(string FileName, int[] Arr, ref int ArrSize)
{
try
{
using (StreamReader InputFile = new StreamReader(@FileName))
{
ArrSize = 0;
while (InputFile.Peek()!= -1)
{
Arr[ArrSize] =
int.Parse(InputFile.ReadLine());
ArrSize++;
}
}
}
catch
{
Console.WriteLine("Ошибка чтения файла");
}
}
В основной программе необходимо описать и проинициализировать одномерный массив с учетом максимально допустимой размерности массива MaxArraySize и вызвать описанный выше метод ReadFromFile ().
static void Main()
{
const int MaxArraySize = 1000;
int[] MyArray = new int[MaxArraySize];
int ArraySize=0;
ReadFromFile("input.txt", MyArray, ref ArraySize);
}
Прежде чем запустить программу, необходимо создать текстовый файл “ input. txt ”, заполнить его целыми числами и сохранить в папке “…\ bin \ debug ” текущего проекта. Важно отметить, что в программе используется построчное считывание данных с помощью метода ReadLine (), поэтому данные во входном файле должны быть введены не в одну строчку, а в столбец (см. рис. 10.2).
При выполнении этого программного кода мы указываем максимальный размер массива MaxArraySize, но это имеет два недостатка. Во-первых, если указать максимальный размер меньше, чем действительное количество элементов в файле, то произойдет ошибка. Во-вторых, если указывать слишком большое значение MaxArraySize, то будет выделяться место в памяти под элементы массива, которые никогда не будут использоваться.
Из этой ситуации имеется такой выход: сначала прочитать файл и узнать, сколько действительно элементов он содержит, а уже потом выделять место в памяти под элементы массива. Метод Main () в таком случае содержит операторы:
int[] MyArray;
ReadFromFile("input.txt", out MyArray);
|
|
Метод ReadFromFile () изменяется так:
static void ReadFromFile(string FileName, out int[] MyArray)
{
try
{
int ArraySize = 0;
using (StreamReader InputFile = new StreamReader(@FileName))
{ //подсчет количества элементов в файле
while (InputFile.Peek()!= -1)
{
InputFile.ReadLine();
ArraySize++;
}
}
//выделение памяти под необходимое число элементов
MyArray = new int[ArraySize];
using (StreamReader InputFile = new StreamReader(@FileName))
{ //инициализация массива
for (int i = 0; i < ArraySize; i++)
MyArray[i] = int.Parse(InputFile.ReadLine());
}
}
catch
{
Console.WriteLine("Ошибка чтения файла");
MyArray = null;
}
}
Заметьте, что массив передается как выходной параметр с использованием ключевого слова out. Поскольку нет гарантии, что операторы в контролируемом блоке try выполнятся без ошибок, блок catch должен содержать оператор, инициализирующий выходной параметр – массив (мы присваиваем значение null).
Этот способ считывания одномерного массива из файла также имеет недостаток – в этом случае по файлу приходится проходить дважды. Как это часто бывает при написании программ, разработчик должен сделать выбор между скоростью работы программы и объемом занимаемой памяти.
Альтернативой предложенному варианту решения является использование метода ReadAllLines () класса File, который считывает все строки файла в массив строк. В этом случае второй проход по файлу не потребуется.
При заполнении двумерного массива воспользуемся таким способом (его можно применить и при работе с одномерным массивом): фактическую размерность массива, т. е. количество строк и столбцов, зададим в начале входного файла, а далее укажем значения его элементов (см. рис. 10.3), поэтому в списке параметров метода ReadFromFile () при передаче в метод двумерного массива MyArray необходимо также указать директиву out, позволяющую инициализировать массив не в основной программе, а непосредственно в теле метода, после считывания его фактической размерности из файла. Отметим, что процесс считывания данных осуществляется построчно с помощью метода ReadLine (). Считанная строка разбивается с помощью метода Split () на массив строк Row. Элементы этого массива переписываются в результирующую матрицу MyArray, преобразуясь из строк в целые числа.
static void ReadFromFile(string FileName, out int[,] MyArray)
{
try
{
using (StreamReader InputFile = new StreamReader(FileName))
{
int RowsCount = int.Parse(InputFile.ReadLine());
int ColsCount = int.Parse(InputFile.ReadLine());
MyArray = new int[RowsCount, ColsCount];
for (int i = 0; i < RowsCount; i++)
{
string[] Row = InputFile.ReadLine().Split();
for (int j = 0; j < ColsCount; j++)
MyArray[i, j] = int.Parse(Row[j]);
}
}
}
catch
{
Console.WriteLine("Ошибка чтения файла");
MyArray = null;
}
}
static void Main(string[] args)
{
int[,] MyArray;
ReadFromFile("input.txt", out MyArray);
}
Классы TextWriter и StreamWriter
Абстрактный класс TextWriter содержит методы, позволяющие производить запись символов в байтовый поток (файл). Основные методы приведены в таблице 10.2.
Таблица 10.2
Методы класса TextWriter
Название | Описание |
Close () | Закрывает файл, освобождает связанные с ним ресурсы |
Write () | Осуществляет запись данных в текстовый поток. Метод позволяет принимать в качестве параметра данные, представленные в разных форматах (целые или вещественные числа, символы или массивы символов, строки, логические переменные) |
WriteLine () | Записывает в текстовый поток данные, за которыми следует признак конца строки |
Класс StreamWriter является классом-наследником класса TextWriter и реализует методы класса-предка.
Конструктор StreamWriter(<ИмяФайла>) создает текстовый поток для записи данных в заданный файл. Отметим, что имя файла может быть задано с указанием пути либо без него. Если файл существует, он будет перезаписан, в противном случае создается новый файл.
Рассмотрим использованием описанных методов на простом примере: вывести значения одномерного массива в файл. Напишем метод WriteToFile (), реализующий вывод элементов массива в заданный файл. Исходя из условия задачи, метод будет принимать в качестве параметров, во-первых, имя заданного файла FileName, во-вторых, одномерный массив MyArray.
static void WriteToFile(string FileName, int[] MyArray)
{
using (StreamWriter OutputFile = new StreamWriter(FileName))
{
foreach (int element in MyArray)
OutputFile.Write("{0} ", element);
}
}
Здесь мы создаем экземпляр класса StreamWriter с именем OutputFile для заданного файла, в цикле foreach последовательно просматриваем все элементы заданного массива и записываем их в файл с помощью метода Write ().
Основная программа включает в себя описание массива с инициализацией и вызов метода WriteToFile ().
static void Main(string[] args)
{
int[] MyArray = {1, 22, 13, -4, 5, 16, 27, 18, -9};
WriteToFile("output.txt", MyArray);
}
Отметим, что в рассмотренном примере значения одномерного целочисленного массива MyArray будут записаны в файл через пробел. Имя файла задано без указания пути к файлу, поэтому, чтобы просмотреть результаты работы программы, необходимо открыть файл “ output. txt ”, который находится в каталоге “…\ bin \ debug \” текущего решения.
Классы Stream и FileStream
Класс Stream является абстрактным базовым классом всех потоков. При работе с потоком допустимы три основные операции:
– чтение из потока – перенос информации из потока в структуру данных, такую как массив байтов;
– запись в поток – передача данных из структуры данных в поток;
– поиск, включающий в себя отправку запросов и изменение текущей позиции внутри потока.
Некоторые свойства и методы класса Stream приведены в таблице 10.3.
Таблица 10.3
Свойства и методы класса Stream
Название | Вид | Описание |
CanRead | неизменяемое свойство | Показывает, можно ли ввести данные из потока |
CanWrite | неизменяемое свойство | Показывает, можно ли записать данные в поток |
CanSeek | неизменяемое свойство | Показывает, поддерживает ли поток произвольный доступ к данным, при котором можно задать текущее положение в потоке |
Close () | экземплярный метод | Закрывает текущий поток и освобождает все связанные с ним ресурсы |
Flush () | экземплярный метод | Записывает данные из буфера в связанный с потоком источник данных (файл) и очищает буфер |
Length | неизменяемое свойство | Возвращает длину потока в байтах |
Position | изменяемое свойство | Возвращает либо задает текущее положение в потоке |
Read () | экземплярный метод | Считывает последовательность байтов определенной длины из потока и записывает их в одномерный массив байт. Возвращает количество прочитанных байтов либо ноль, если достигнут конец потока |
ReadByte () | экземплярный метод | Возвращает целочисленное представление следующего байта из потока либо –1, если достигнут конец потока |
ReadTimeOut | изменяемое свойство | Возвращает или задает продолжительность времени ожидания в миллисекундах в процессе чтения данных из потока |
Seek () | экземплярный метод | Перемещает указатель текущего положения в потоке на заданную позицию |
Write () | экземплярный метод | Записывает последовательность байт определенной длины в массив, начиная с указанного индекса, и перемещает указатель в потоке на количество записанных байт |
WriteByte () | экземплярный метод | Записывает один байт в поток и перемещает указатель в потоке на один байт |
WriteTimeOut | изменяемое свойство | Возвращает или задает продолжительность времени ожидания в миллисекундах в процессе записи данных в поток |
Ввод/вывод, организованный на уровне байтов, называется байтовым потоком. Для создания байтового потока служит класс FileStream, являющийся классом-наследником класса Stream и реализующий рассмотренные выше методы и свойства. Конструктор класса перегружен и имеет несколько вариантов реализации, мы рассмотрим только одну из них. FileStream(<ИмяФайла>,<Режим>,<ТипДоступа>)- создает экземпляр класса FileStream, связывает его с заданным файлом и открывает файл в заданном режиме с заданным типом доступа. Поясним, что любой файл можно открывать в различных режимах. При этом режим открытия файла задается одним из константных значений перечисления FileMode:
– Append – указывает, что операционная система (ОС) должна открыть существующий файл для записи, при этом текущий указатель положения в файле устанавливается в конец файла. Если файл не существует, создает новый файл;
– Create – указывает, что ОС должна создать новый файл. Если в каталоге уже существует файл с заданным именем, он будет переписан;
– CreateNew – указывает, что ОС должна создать новый файл. Если в каталоге уже существует файл с заданным именем, генерируется исключение IOException;
– Open – указывает, что ОС должна открыть существующий файл. Если файл с заданным именем не существует, генерируется исключение FileNotFoudException;
– OpenOrCreate – указывает, что ОС должна открыть файл с заданным именем, если он существует, либо создать файл с заданным именем в противном случае;
– Truncate – указывает, что ОС должна открыть существующий файл. После открытия он должен быть обрезан до нулевой длины. Файл может быть открыт только для записи.
Кроме режима открытия фала, в конструкторе необходимо указать тип доступа к нему. Этот параметр принимает одно из константных значений перечисления FileAccess:
– Read – указывает, что файл открыт только для чтения;
– Write – указывает, что файл открыт только для записи;
– ReadWrite – указывает, что файл доступен и для чтения и для записи.
Приведем простой пример использования байтовых потоков для записи данных в файл и считывания данных из файла.
try
{
FileStream InputFile = new FileStream("input.txt", FileMode.Create, FileAccess.Write);
for (byte i = 0; i < 100; i++)
InputFile.WriteByte(i);
InputFile.Close();
}
catch
{
Console.WriteLine("Ошибка ввода");
}
Здесь мы создаем файл “ input. txt ” и открываем его для записи. Далее записываем в него целые числа от 0 до 99. Важно заметить, что при открытии созданного текстового файла вы не увидите числа от 0 до 99. Это связано с тем, что данные записаны в файл на уровне байт, и не преобразованы в символьный формат, удобный для восприятия человеком.
В следующем примере мы открываем созданный файл “ input. txt ” для чтения и выводим считанные данные на экран.
try
{
FileStream InputFile = new FileStream("input.txt",
FileMode.Open, FileAccess.Read);
int b = InputFile.ReadByte();
while (b!= -1)
{
Console.WriteLine(b);
b = InputFile.ReadByte();
}
InputFile.Close();
}
catch
{
Console.WriteLine("Ошибка чтения файла");
}
В результате работы программы на экран будет выведена последовательность чисел от 0 до 99.
Используя метод Seek () для позиционирования указателя текущего положения в файле, можно управлять процессом считывания данных из файла. Синтаксис: Seek(long offset, SeekOrigin origin). Здесь параметр offset задает смещение в байтах относительно точки отсчета положения в потоке. Точка отсчета задается параметром origin и может принимать одно из константных значений перечисления SeekOrigin: Begin – начало файла, Current – текущее положение, End – конец файла.
Следующая модификация рассмотренного выше примера, используя метод Seek (), считывает лишь каждый третий байт из потока.
try
{
FileStream InputFile = new FileStream("input.txt",
FileMode.Open, FileAccess.Read);
int b = InputFile.ReadByte();
while (b!= -1)
{
Console.WriteLine(b);
InputFile.Seek(2, SeekOrigin.Current);
b = InputFile.ReadByte();
}
InputFile.Close();
}
catch
{
Console.WriteLine("Ошибка чтения файла");
}
Подчеркнем, что байтовый поток удобно использовать только для решения задач, в которых операции с файлами реализуются программным путем и пользователю не требуется самому изменять содержимое файлов. Если же необходимо считать из файла данные, введенные пользователем и представленные в символьном формате, удобнее пользоваться классами StreamReader и StreamWriter. Напомним, что эти классы, по сути, являются лишь надстройкой над классом Stream, они тоже используют байтовые потоки для чтения данных из файла и записи данных в файл, но при этом выполняют автоматическое преобразование байтового потока в символьный и наоборот (рис. 10.4).
|
Классы BinaryReader и BinaryWriter
Кроме байтовых и символьных потоков в языке C # реализованы двоичные потоки для работы с примитивными типами данных, такими как int, double, short и др. Двоичные потоки хранят значения в двоичном коде и не предназначены для просмотра их человеком.
Двоичный поток открывается на основе базового потока, который, в частности может быть инициализирован с помощью класса FileStream.
Так конструктор BinaryReader(<ИмяБазовогоПотока>) открывает двоичный поток для считывания данных из файла, ассоциированного с заданным базовым потоком.
Некоторые свойства и методы класса BinaryReader приведены в таблице 5.
Таблица 10.5
Свойства и методы класса BinaryReader
Название | Вид | Описание |
BaseStream | неизменяемое свойство | Предоставляет доступ к базовому потоку |
Close () | экземплярный метод | Закрывает текущий и связанный с ним базовый поток |
PeekChar () | экземплярный метод | Возвращает следующий доступный для чтения символ в формате int, не перемещая внутренний указатель текущего положения в потоке. Если доступного для чтения символа нет, возвращает значение –1 |
Read () | экземплярный метод | Считывает текущий символ из базового потока и перемещает внутренний указатель потока на один символ, либо возвращает значение –1, если доступных для чтения символов нет |
Метод Read () перегружен и имеет несколько вариантов реализации. Так, его можно использовать для считывания из базового потока последовательности байтов определенной длины и записи их в одномерный массив с указанного индекса; метод при этом возвращает количество считанный из потока байт, либо ноль, если достигнут конец потока. Также метод Read () может считывать из базового потока последовательность символов.
Кроме того, в классе BinaryReader определены методы, реализующие считывание из базового потока значений всех встроенных в C # примитивных типов. Синтаксис таких методов одинаков и имеет вид: ReadXX (), где вместо XX указывается соответствующий тип данных. Например, метод ReadBoolean () считывает из базового потока величину типа Boolean (bool) и перемещает внутренний указатель потока на соответствующее количество байт вперед в зависимости от типа считанной величины, в данном случае на один байт.
Конструктор BinaryWriter(<ИмяБазовогоПотока>) открывает двоичный поток для записи данных в файл, ассоциированный с заданным базовым потоком. Основные методы приведены в таблице 10.6.
Таблица 10.6
Методы класса BinaryWriter
Название | Описание |
Flush () | Записывает данные из буфера в связанный с потоком источник данных (файл) и очищает буфер |
Seek () | Устанавливает позицию указателя в текущем потоке (имеет тот же синтаксис, что и аналогичный метод класса Stream) |
Write () | Записывает заданное значение в текущий поток |
Close () | Закрывает текущий и связанный с ним базовый поток |
Приведем пример записи в файл и чтения из файла данных разных типов с использованием двоичных потоков.
try
{
FileStream MyFile = new FileStream("input.txt", FileMode.Create,
FileAccess.Write);
BinaryWriter MyBinaryFile = new BinaryWriter(MyFile);
MyBinaryFile.Write("Ivanov");
MyBinaryFile.Write("PM-11");
MyBinaryFile.Write(1);
MyBinaryFile.Write(true);
MyBinaryFile.Write("Petrov");
MyBinaryFile.Write("PM-21");
MyBinaryFile.Write(2);
MyBinaryFile.Write(false);
MyBinaryFile.Close();
}
catch
{
Console.WriteLine("Ошибка ввода");
}
Здесь мы записываем в двоичный поток информацию о двух студентах: фамилию, группу, курс и логическую величину, указывающую, проживает ли студент в общежитии.
Считывание введенных таким образом данных в файл, можно организовать следующим образом:
try
{
FileStream MyFile = new FileStream("input.txt", FileMode.Open,
FileAccess.Read);
BinaryReader MyBinaryFile = new BinaryReader(MyFile);
Console.Write("Name: " + MyBinaryFile.ReadString());
Console.Write("\tGroup: " + MyBinaryFile.ReadString());
Console.Write("\tCourse: " + MyBinaryFile.ReadInt32());
Console.WriteLine("\tLiving in a hostel: " +
MyBinaryFile.ReadBoolean());
Console.Write("Name: " + MyBinaryFile.ReadString());
Console.Write("\tGroup: " + MyBinaryFile.ReadString());
Console.Write("\tCourse: " + MyBinaryFile.ReadInt32());
Console.WriteLine("\tLiving in a hostel: " +
MyBinaryFile.ReadBoolean());
MyBinaryFile.Close();
}
catch
{
Console.WriteLine("Ошибка ввода");
}
Отметим, что для проверки работоспособности программы, считанные из файла данные, выводятся на экран, поскольку файлы, созданные с помощью класса BinaryWriter, не предназначены для просмотра человеком, а обычно создаются для использования в программах.
Классы FileInfo и DirectoryInfo
Методы работы с файлами не ограничиваются только манипуляциями с данными в процессе ввода/вывода, а включают в себя также такие операции как копирование, перемещение, удаление, вывод информации об объекте файловой системы – файле или каталоге. Для реализации этих операций в языке C # предусмотрены следующие классы:
– статический класс Directory и экземплярный класс DirectoryInfo позволяют создавать, копировать, перемещать, переименовывать и удалять каталоги, а также получать и задавать сведения о типе DateTime, относящиеся к созданию, доступу и записи каталога;
– статический класс File и экземплярный класс FileInfo позволяют создавать, копировать, удалять, перемещать и открывать файлы, а также может использоваться при инициализации объектов FileStream.
В таблице 10.7 приведены некоторые свойства и методы класса FileSystemInfo, являющегося базовым для классов FileInfo и DirectoryInfo (см. рис. 10.1).
Таблица 10.7
Свойства и методы класса FileSystemInfo
Название | Вид | Описание |
Attributes | изменяемое свойство | Возвращает или устанавливает атрибуты для данного объекта файловой системы. Использует константные значения перечисления FileAttributes |
CreationTime | изменяемое свойство | Получает или задает время создания объекта файловой системы |
Delete () | экземплярный метод | Удаляет текущий объект файловой системы – файл или каталог – без возможности его восстановления |
Exists | неизменяемое свойство | Определяет, существует ли данный объект файловой системы |
Extension | неизменяемое свойство | Возвращает расширение файла в строковом формате |
FullName | неизменяемое свойство | Возвращает имя файла или каталога с указанием полного пути |
LastAccessTime | изменяемое свойство | Позволяет получить или задать время последнего обращения к объекту файловой системы |
LastWriteTime | изменяемое свойство | Позволяет получить или установить время последней операции записи в объект файловой системы |
Name | неизменяемое свойство | Для файлов возвращает имя файла, для каталогов – имя последнего каталога в иерархии, если таковая существует, а если нет, возвращает имя текущего каталога |
Refresh () | экземплярный метод | Обновляет состояние объекта. Используется перед попыткой получения сведений об атрибуте текущего объекта, иначе сведения могут оказаться устаревшими |
Как уже было сказано, описанные свойства и методы реализуются классами-наследниками FileInfo и DirectoryInfo.
Конструктор класса FileInfo(<ИмяФайла>) создает экземпляр класса и ассоциирует его с заданным файлом. Рассмотрим некоторые его методы (таблица 10.8).
Таблица 10.8
Методы класса FileInfo
Название | Описание |
CopyTo () | Первый вариант: если в параметрах указано только имя файла, то копирует текущий файл в новый файл, имя которого задано в качестве параметра. В случае если создаваемый файл уже существует, его перезапись недопустима. Второй вариант: если кроме имени файла указан логический параметр, метод копирует текущий файл в новый файл и позволяет разрешить или запретить перезапись файла |
Create () | Создает файл и инициализирует байтовый поток класса FileStream, связанный с заданным файлом |
MoveTo () | Перемещает текущий файл в новое местоположение и разрешает переименование файла |
Конструктор DirectoryInfo(<Путь>) выполняет инициализацию нового экземпляра класса для заданного пути. Рассмотрим некоторые его методы (таблица 10.10).
Таблица 10.10
Методы класса DirectoryInfo
Название | Описание |
Create () | Создает каталог, заданный в конструкторе класса. Если такой каталог уже существует, метод ничего не выполняет |
CreateSubDirectory () | Создает один или несколько подкаталогов по заданному пути |
GetDirectories () | Возвращает подкаталоги текущего каталога в виде массива объектов класса DirectoryInfo |
GetFiles () | Первый вариант: позволяет получить файлы в текущем каталоге в виде массива объектов класса FileInfo. Второй вариант: если указано имя файла, метод позволяет осуществить поиск всех файлов, имена которых удовлетворяют некоторому условию поиска, заданному в строковом формате, и возвращает массив объектов класса FileInfo, либо null если таких файлов нет. При указании в параметре имени файла, разрешено использовать стандартные подстановочные знаки ‘*’ и ‘?’, используемые при поиске |
MoveTo () | Перемещает каталог и все его содержимое в местоположение, на которое указывает новый путь |
Кроме методов, у класса DirectoryInfo есть свойство Parent, которое возвращает родительский каталог текущего подкаталога, либо значение null, если путь к файлу пуст или указывает на корневой каталог.
Рассмотрим использование описанных методов и свойств классов FileInfo и DirectoryInfo на примере решения следующей задачи: удалить все файлы с заданным расширением из заданного каталога, включая подкаталоги.
Введем переменные DirName для хранения имени заданного пользователем каталога и FileName для хранения имени файла, считаем их с клавиатуры.
Console.WriteLine("Введите имя каталога");
string DirName = Console.ReadLine();
Console.WriteLine("Введите расширение файлов, которые надо удалить");
string FileName = Console.ReadLine();
Далее опишем метод DeleteFiles () следующим образом:
static void DeleteFiles(string DirName, string FileName)
{
try
{
DirectoryInfo MyDir = new DirectoryInfo(@DirName);
if (!MyDir.Exists) //проверка существования каталога
{
Console.WriteLine("Каталог " + MyDir.Name +
" не существует");
return;
}
DelFiles(MyDir, FileName);
Console.WriteLine("Удаление завершено");
}
catch
{
Console.WriteLine("Ошибка");
}
}
Здесь мы инициализируем экземпляр класса DirectoryInfo и связываем его с заданным пользователем каталогом. В случае если заданный каталог не существует, выводим соответствующее сообщение на экран. Если же такой каталог существует, вызываем рекурсивный метод DelFiles (), реализующий удаление файлов.
static void DelFiles(DirectoryInfo MyDir, string FileName)
{
//вывод полного имени текущего каталога на экран
Console.WriteLine(MyDir.FullName);
//удаление всех файлов с заданным именем
FileInfo[] files = MyDir.GetFiles(FileName);
foreach (FileInfo f in files) f.Delete();
//просмотр всех вложенных в текущий каталог подкаталогов
DirectoryInfo[] SubDirs = MyDir.GetDirectories();
foreach (DirectoryInfo d in SubDirs) DelFiles(d,FileName);
}
Здесь поиск файлов, удовлетворяющих некоторому условию поиска, например по заданному расширению, осуществляется с помощью метода GetFiles (), определенного в классе DirectoryInfo. Так, например, для поиска всех текстовых файлов необходимо задать в качестве условия поиска строку “*. txt ”.
Для получения всех каталогов, вложенных в текущий, используется метод GetDirectories (). Далее для каждого из найденных подкаталогов процесс поиска и удаления файлов повторяется за счет рекурсивного вызова исходного метода.
Задания для самостоятельного выполнения:
Часть А. Каждую из этих задач решите с использованием классов StreamReader и StreamWriter. Реализуйте в консольном приложении.
1. Во входном файле input.txt в столбец записаны целые числа. В файл output.txt переписать исходные данные, а в последнюю строку – их среднее арифметическое.
2. Во входном файле input.txt в столбец записаны целые числа. В файл output.txt переписать исходные данные, а в последнюю строку – наибольшее из значений, если их несколько, то также указать количество таких элементов.
3. Во входном файле input.txt в столбец записаны целые числа. В файл out1.txt записать все четные числа, а в файл out2.txt – все нечетные.
4. Дан текстовый файл input.txt. Вставить в начало каждой строки ее номер и записать преобразованные строки в файл output.txt.
5. Во входном файле input.txt содержится произвольное количество строк, в каждой из которых записана некоторая последовательность целых чисел, разделенных пробелом. В файл output.txt вывести следующие данные о файле input.txt: количество строк и количество элементов в каждой строке.
6. Во входном файле input.txt содержится произвольное количество строк, в каждой из которых записана некоторая последовательность целых чисел, разделенных пробелом. В файл output.txt вывести максимальный элемент в каждой строке.
7. Во входном файле input.txt содержится текст, записанный строчными английскими буквами. В файл output.txt вывести этот же текст, записанный заглавными буквами.
8. Во входном файле input.txt содержится текст. В файл output.txt вывести отредактированный текст, в котором удалены лишние пробелы между словами.
9. Во входном файле input.txt содержится текст. В файл output.txt вывести в алфавитном порядке все буквы, встречающиеся в тексте и их количество.
10. Во входном файле input.txt содержится текст. В файл output.txt вывести отредактированный текст, в котором удалены все пробелы перед знаками препинания (кроме тире), а после знака препинания стоит только один пробел. Первое слово в предложении должно начинаться с заглавной буквы.
11. Во входном файле input.txt содержится текст. В файл output.txt вывести текст в зашифрованном виде: каждая буква исходного текста заменяется на следующую за ней в алфавите (буква ‘ z ’ заменяется на ‘ а ’).
Часть B. Каждую из этих задач решите тремя способами – с использованием символьного, байтового и двоичного потоков. Реализуйте в консольном приложении.
12. Создать программным образом файл input.txt и заполнить его 20 целыми числами, полученными с помощью генератора случайных чисел из диапазона [–50, 50]. Написать программу, выводящую на экран и в файл output.txt только положительные числа из файла input.txt.
13. Создать программным образом файл input.txt и заполнить его 30 целыми числами, полученными с помощью генератора случайных чисел из диапазона [–20, 40]. Написать программу, выводящую на экран и в файл output.txt максимальный и минимальный элементы файла input.txt.
14. Создать программным образом файл input.txt и заполнить его 40 целыми числами, полученными с помощью генератора случайных чисел из диапазона [0, 100]. Написать программу, выводящую на экран и в файл output.txt только те элементы файла input.txt, в записи которых есть заданная цифра (вводится с клавиатуры).
15. Создать программным образом файл input.txt и заполнить его 50 целыми числами, полученными с помощью генератора случайных чисел из диапазона [–150, 250]. Написать программу, выводящую на экран и в файл output.txt числа из файла input.txt, исключив повторные вхождения.
Часть C. Каждую из этих задач решите с использованием классов FileInfo и/или DirectoryInfo. Реализуйте в консольном приложении.
16. Написать программу, позволяющую создавать заданный пользователем каталог. Если каталог с таким именем уже существует, то вывести сообщение об этом пользователю.
17. Написать программу, выводящую на экран иерархический список всех подкаталогов, вложенных в каталог, заданный пользователем.
18. Написать программу, которая проверяет, существует ли заданный пользователем файл и если файл существует, выводит на экран информацию о времени создания файла и о времени последнего обращения к файлу.
19. Написать программу, которая выводит на экран список файлов MS Word, содержащихся в заданном каталоге.
20. Написать программу, которая для заданного пользователем каталога создает подкаталоги, соответствующие дате создания каждого отдельно взятого файла, и перемещает каждый файл в соответствующий дате каталог.
21. Написать программу, которая удаляет все подкаталоги, вложенные в заданный.