15. 15
Чтение информации о дисках
AvailableFreeSpace
DriveFormat
DriveType
IsReady
Name
TotalFreeSpace
TotalSize
VolumeLabel
Editor's Notes
Пространство имен System.IO в .NET — это область библиотек базовых классов, посвященная службам файлового ввода-вывода, а также ввода-вывода из памяти.
Подобно любому пространству имен, в System.IO определен набор классов, интерфейсов, перечислений, структур и делегатов.
Многие типы из пространства имен System.IO сосредоточены на программных манипуляциях физическими каталогами и файлами. Дополнительные типы предоставляют поддержку чтения и записи данных в строковые буферы, а также области памяти.
Ниже кратко описаны основные (неабстрактные) классы, которые дают понятие о функциональности System.IO:
BinaryReader, BinaryWriter. Эти классы позволяют сохранять и извлекать элементарные типы данных (целочисленные, булевские, строковые и т.п.) в двоичном виде
BufferedStream. Этот класс предоставляет временное хранилище для потока байтов, которые могут затем быть перенесены в постоянные хранилища
Directory, DirectoryInfo. Эти классы используются для манипуляций структурой каталогов машины. Тип Directory представляет функциональность, используя статические члены. Тип DirectoryInfo обеспечивает аналогичную функциональность через действительную объектную ссылку
DriveInfo. Этот класс предоставляет детальную информацию относительно дисковых устройств, используемых данной машиной
File, FileInfo. Эти классы служат для манипуляций множеством файлов данной машины. Тип File представляет функциональность через статические члены. Тип FileInfo обеспечивает аналогичную функциональность через действительную объектную ссылку
FileStream. Этот класс обеспечивает произвольный доступ к файлу (т.е. возможности поиска) с данными, представленными в виде потока байт
FileSystemWatcher. Этот класс позволяет отслеживать модификации внешних файлов в определенном каталоге
MemoryStream. Этот класс обеспечивает произвольный доступ к данным, хранящимся в памяти, а не в физическом файле
Path. Этот класс выполняет операции над типами System.String, содержащими информацию о пути к файлу или каталогу в независимой от платформы манере
StreamWriter, StreamReader. Эти классы используются для хранения (и извлечения) текстовой информации из файла. Эти классы не поддерживают произвольного доступа к файлу
StringWriter, StringReader. Подобно классам StreamWriter/StreamReader, эти классы также работают с текстовой информацией. Однако лежащим в основе хранилищем является строковый буфер, а не физический файл
--------------------------------------------------
Как не трудно было заметить в приведенном выше списке, для представления файлов и папок используются по два класса. Какой из них применять — во многом зависит от того, сколько раз требуется получить доступ к данной папке или файлу.
Основным для потоков является класс System.IO.Stream. Он представляет байтовый поток и является базовым для всех остальных классов потоков. Кроме того, он является абстрактным классом, а это означает, что получить экземпляр объекта класса Stream нельзя.
В абстрактном классе System.IO.Stream определен набор членов, которые обеспечивают поддержку синхронного и асинхронного взаимодействия с хранилищем (например, файлом или областью памяти).
Концепция потока не ограничена файловым вводом-выводом. Точности ради следует отметить, что библиотеки .NET предоставляют потоковый доступ к сетям, областям памяти и прочим абстракциям, связанным с потоками.
Опять-таки, потомки класса Stream представляют данные, как низкоуровневые потоки байт, а непосредственная работа с низкоуровневыми потоками может оказаться довольно загадочной. Некоторые типы, унаследованные от Stream, поддерживают поиск (seeking), что означает возможность получения и изменения текущей позиции в потоке.
Для чтения и записи в память предусмотрен специальный базовый класс .NET по имени System.IO.MemoryStream, а для работы с сетевыми данными — класс System.Net.Sockets.NetworkStream. Для чтения и записи данных в именованные каналы никаких базовых потоковых классов в .NET не предлагается, но зато имеется один обобщенный потоковый класс System.IO.Stream, от которого можно наследовать нужный класс. Никаких предположений касательно природы внешнего источника данных в классе Stream не делается.
В роли внешнего источника может даже выступать переменная внутри кода. Как бы парадоксально это не звучало, но прием с использованием потоков для передачи данных между переменными может оказываться очень удобным способом для преобразования данных из одного типа в другой. Для преобразования данных из числовых в строковые или для форматирования строк в языке С применялось нечто подобное — функция sprintf.
Преимущество использования отдельного объекта для передачи данных вместо применения для этого классов FileInfo или DirectoryInfo состоит в том, что разделение понятий передачи данных и конкретного источника существенно упрощает изменение этих источников. В самих объектах потоков содержится масса обобщенного кода, касающегося перемещения данных между внешними источниками и переменными в создаваемом самостоятельно коде. Не смешивая этот код с концепцией определенного источника данных, можно значительно повысить возможность его многократного использования в разных обстоятельствах (через наследование).
Например, классы StringReader и StringWriter являются частью одного и того же дерева наследования, как и те два класса, которые будут применяться чуть позже для чтения и записи текстовых файлов. Все классы почти всегда "за кулисами" будут разделять приличную часть кода.
Класс FileStream предоставляет реализацию абстрактного члена Stream в манере, подходящей для потоковой работы с файлами. Это элементарный поток, и он может записывать или читать только один байт или массив байтов. Однако взаимодействовать с членами типа FileStream придется нечасто. Вместо этого, скорее всего, будут использоваться оболочки потоков, которые облегчают работу с текстовыми данными или типами .NET. Тем не менее, в целях иллюстрации полезно поэкспериментировать с возможностями синхронного чтения/записи типа FileStream.
Экземпляр FileStream применяется для чтения и записи данных в любой файл. Для создания экземпляра FileStream потребуется указать следующие фрагменты информации:
Файл, к которому должен получаться доступ.
Режим открытия файла. Например, планируется создать новый файл или же открыть существующий? Если планируется открыть существующий файл, то должно ли перезаписываться имеющееся в нем содержимое, или новые данные должны добавляться к концу файла?
Вид доступа к файлу. Например, нужен ли доступ для выполнения чтения или записи либо того и другого вместе?
Общий доступ, который показывает, должен ли доступ к файлу быть эксклюзивным или же должна быть возможность доступа со стороны других потоков одновременно. Если да, то разрешено ли другим потокам чтение, запись либо то и другое.
Обратите внимание, что в случае FileMode, если запрашивается режим, не соответствующий существующему состоянию файла, может быть сгенерировано исключение.
Значения Append, Open и Truncate будут приводить к генерации исключения, если файл не существует, а значение CreateNew — наоборот, если он уже существует.
Значения Create и OpenOrCreate подходят в обоих сценариях, но Create приводит к удалению любого существующего файла и замене его новым, изначально пустым.
exmpl_10
Как видно в коде, перегруженные версии данных конструкторов способны подставлять стандартные значения FileAccess.ReadWrite и FileShare.Read на месте третьего и четвертого параметров в зависимости от значения FileMode. Можно также создавать файловый поток из экземпляра FileInfo.
fs.Close();
Закрытие потока приводит к освобождению всех ассоциированных с ним ресурсов и позволяет другим приложениям запускать потоки для работы с тем же файлом. Кроме того, это действие приводит к очистке буфера. Между открытием и закрытием потока нужно производить собственно чтение и/или запись данных. В классе FileStream для этого предусмотрен набор методов.
Метод ReadByte() представляет собой самый простой способ для чтения данных. Он берет один байт из потока и приводит результат к типу int со значением в диапазоне от О до 255. В случае достижения конца потока он возвращает -1.
Если необходимо, чтобы за один раз читалось сразу множество байтов, можно вызывать метод Read(), который читает указанное количество байтов в массив. Метод Read() возвращает действительное количество прочитанных байтов; если возвращается значение О, значит, был достигнут конец потока.
Для класса Path создавать экземпляры не нужно. Он предоставляет статические методы, которые упрощают выполнение операций с путевыми именами. Например, предположим, что требуется отобразить полное путевое имя для файла ReadMe.txt в папке С:\МуDocuments.
Console.WriteLine(Path.Combine(@"C:\My Documents", "ReadMe.txt"));
Использовать класс Path гораздо легче, чем пытаться вручную производить разбор, особенно с учетом того, что классу Path известно, как выглядят различные форматы путевых имен в разных операционных системах. На момент написания этой статьи единственной поддерживаемой .NET операционной системой была Windows. Однако после переноса .NET в Unix класс Path будет справляться с представлением путей в Unix, где в качестве разделительного символа применяется /, а не \.
Чаще других в этом классе используется метод Combine(), но есть и другие методы, которые позволяют получать информацию о пути и требуемом для него формате.
Ниже перечислены некоторые статические поля класса Path:
AltDirectorySeparatorCharПозволяет не зависящим от платформы образом указывать альтернативный символ для разделения уровней каталогов. В Windows применяется символ /, а в Unix — символ \.
DirectorySeparatorCharПозволяет не зависящим от платформы образом указывать символ для разделения уровней каталогов. В Windows по умолчанию применяется символ /, а в Unix — символ \.
PathSeparatorПозволяет не зависящим от платформы образом указывать символ, который должен использоваться в путевых строках для разделения переменных среды. По умолчанию это точка с запятой.
VolumeSeparatorCharПозволяет не зависящим от платформы образом указывать символ, который должен использоваться в путях для разделения томов. По умолчанию это двоеточие.
Класс FileInfo позволяет получать подробности относительно существующих файлов на жестком диске (т.е. время создания, размер и атрибуты) и предназначен для создания, копирования, перемещения и удаления файлов. Вдобавок к набору функциональности, унаследованной от FileSystemInfo, есть некоторые члены, уникальные для класса FileInfo, которые описаны ниже:
AppendText(). Создает объект StreamWriter и добавляет текст в файл
СоруТо() . Копирует существующий файл в новый файл
Create() . Создает новый файл и возвращает объект FileStream для взаимодействия с вновь созданным файлом
CreateText() . Создает объект StreamWriter, записывающий новый текстовый файл
Delete() . Удаляет файл, к которому привязан экземпляр FileInfo
Directory. Получает экземпляр родительского каталога
DirectoryName. Получает полный путь к родительскому каталогу
Length. Получает размер текущего файла или каталога
MoveTo() . Перемещает указанный файл в новое местоположение, предоставляя возможность указать новое имя файла
Name. Получает имя файла
Open() . Открывает файл с различными привилегиями чтения/записи и совместного доступа
OpenRead() . Создает доступный только для чтения объект FileStream
OpenText() . Создает объект StreamReader и читает из существующего текстового файла
OpenWrite() . Создает доступный только для записи объект FileStream
Давайте изучим различные способы получения дескриптора файла с использованием класса FileInfo.
exmpl_01
Метод FileInfo.Create() возвращает тип FileStream, который предоставляет синхронную и асинхронную операции записи/чтения лежащего в его основе файла. Имейте в виду, что объект FileStream, возвращенный FileInfo.Create() открывает полный доступ по чтению и записи всем пользователям.
С помощью метода FileInfo.Open() можно открывать существующие файлы, а также создавать новые файлы с гораздо более высокой точностью, чем FileInfo.Create(), учитывая, что Open() обычно принимает несколько параметров для описания общей структуры файла, с которым будет производиться работа. В результате вызова Open() получается возвращенный им объект FileStream.
exmpl_02
Хотя метод FileInfo.Open() позволяет получить дескриптор файла довольно гибким способом, в классе FileInfo также предусмотрены для этого члены OpenRead() и OpenWrite(). Как и можно было ожидать, эти методы возвращают объект FileStream, соответствующим образом сконфигурированный только для чтения или только для записи, без необходимости применять различные значения перечислений.
Еще один член типа FileInfo, связанный с открытием файлов — OpenText(). В отличие от Create(), Open(), OpenRead() и OpenWrite(), метод OpenText() возвращает экземпляр типа StreamReader, а не FileStream.
Тип File предоставляет функциональность, почти идентичную типу FileInfo, с помощью нескольких статических методов. Подобно FileInfo, тип File поддерживает методы AppendText(), Create(), CreateText(), Open(), OpenRead(), OpenWrite() и OpenText(). Тип File также поддерживает несколько уникальных членов, перечисленных ниже, которые могут значительно упростить процесс чтения и записи текстовых данных:
ReadAllBytes(). Открывает указанный файл, возвращает двоичные данные в виде массива байт и затем закрывает файл
ReadAllLines() . Открывает указанный файл, возвращает символьные данные в виде массива строк, затем закрывает файл
ReadAllText() . Открывает указанный файл, возвращает символьные данные в виде System.String(), затем закрывает файл
WriteAllBytes() . Открывает указанный файл, записывает в него байтовый массив и закрывает файл
WriteAllLines() . Открывает указанный файл, записывает в него массив строк и закрывает файл
WriteAllText() . Открывает указанный файл, записывает в него данные из указанной строки и закрывает файл
Отсюда вывод: когда необходимо быстро получить файловый дескриптор, тип File сэкономит некоторый объем ввода. Однако преимущество предварительного создания объекта FileInfo связано с возможностью исследования файла с использованием членов абстрактного базового класса FileSystemInfo.
DirectoryInfo содержит набор членов, используемых для создания, перемещения, удаления и перечисления каталогов и подкаталогов. В дополнение к функциональности, предоставленной базовым классом (FileSystemInfo), DirectoryInfo предлагает ключевые члены, перечисленные ниже:
Create(), CreateSubdirectorу(). Создает каталог (или набор подкаталогов) по заданному путевому имени
Delete() . Удаляет каталог и все его содержимое
GetDirectories() . Возвращает массив объектов DirectoryInfo, представляющих все подкаталоги в текущем каталоге
GetFiles() . Извлекает массив объектов FileInfo, представляющий множество файлов в заданном каталоге
MoveTo() . Перемещает каталог со всем содержимым по новому пути
Parent. Извлекает родительский каталог данного каталога
Root. Получает корневую часть пути
Работа с типом DirectoryInfo начинается с указания определенного пути в качестве параметра конструктора. Если требуется получить доступ к текущему рабочему каталогу (т.е. каталогу выполняющегося приложения), применяйте нотацию ".". Вот некоторые примеры:
exmpl_03
-------------------------------------------------------------------------------------------------------
Во втором примере делается предположение, что переданный в конструктор путь (C:\Windows) физически существует на машине. При попытке взаимодействовать с несуществующим каталогом будет сгенерировано исключение System.IO.DirectoryNotFoundException. Таким образом, чтобы указать каталог, который пока еще не создан, сначала придется вызвать метод Create():
exmpl_04
-------------------------------------------------------------------------------------------------------
После создания объекта DirectoryInfo можно исследовать его содержимое, используя любое свойство, унаследованное от FileSystemInfo. Например:
exmpl_05
-----------------------------------------------------------------------------------------------------
В дополнение к получению базовых деталей о существующем каталоге можно расширить текущий пример использованием некоторых методов типа DirectoryInfo. Для начала применим метод GetFiles() для получения информации обо всех файлах *.jpg, расположенных в каталоге C:\Windows\Web\Wallpaper. Если на вашей машине нет каталога C:\Windows\Web\Wallpaper, измените код для чтения файлов из какого-то существующего каталога (например, прочитайте все файлы *.bmp из каталога C:\Windows).
Метод GetFiles() возвращает массив объектов типа FileInfo, каждый из которых представляет детальную информацию о конкретном файле. Предположим, что следующий статический метод класса Program вызывается в методе Main():
exmpl_06
-----------------------------------------------------------------------------------------------------
Обратите внимание на указание в вызове GetFiles() опции поиска; SearchOption.AllDirectories обеспечивает просмотр всех подкаталогов корня. После запуска этого приложения получается список файлов, отвечающих критерию поиска.
После опробования типа DirectoryInfo в действии можно приступать к изучению типа Directory. По большей части статические члены Directory повторяют функциональность, предоставленную членами уровня экземпляра, которые определены в DirectoryInfo. Вспомните, однако, что члены Directory обычно возвращают строковые данные вместо строго типизированных объектов Filelnfo/DirectoryInfo.
Пространство имен System.IO включает класс DriveInfo. Подобно Directory.GetLogicalDrives(), статический метод DriveInfo.GetDrives() позволяет получить имена дисковых приводов машины.
Однако, в отличие от Directory.GetLogicalDrives(), DriveInfo предоставляет множество дополнительных деталей (таких как тип привода, доступное свободное пространство и метка тома).
Класс FileStream не очень удобно применять для работы с текстовыми файлами. К тому же для этого в пространстве System.IO определены специальные классы: StreamReader и StreamWriter.
Чтение из файла и StreamReader
Класс StreamReader позволяет нам легко считывать весь текст или отдельные строки из текстового файла. Среди его методов можно выделить следующие:
Close: закрывает считываемый файл и освобождает все ресурсы
Peek: возвращает следующий доступный символ, если символов больше нет, то возвращает -1
Read: считывает и возвращает следующий символ в численном представлении. Имеет перегруженную версию: Read(char[] array, int index, int count), где array - массив, куда считываются символы, index - индекс в массиве array, начиная с которого записываются считываемые символы, и count - максимальное количество считываемых символов
ReadLine: считывает одну строку в файле
ReadToEnd: считывает весь текст из файла
Считаем текст из файла различными способами:
exmpl_07
Обратите внимание, что в последних двух случаях в конструкторе StreamReader указывалась кодировка System.Text.Encoding.Default. Свойство Default класса Encoding получает кодировку для текущей кодовой страницы ANSI. Также через другие свойства мы можем указать другие кодировки. Если кодировка не указана, то при чтении используется UTF8. Иногда важно указывать кодировку, так как она может отличаться от UTF8, и тогда мы получим некорректный вывод.
Для записи в текстовый файл используется класс StreamWriter. Свою функциональность он реализует через следующие методы:
Close: закрывает записываемый файл и освобождает все ресурсы
Flush: записывает в файл оставшиеся в буфере данные и очищает буфер.
Write: записывает в файл данные простейших типов, как int, double, char, string и т.д.
WriteLine: также записывает данные, только после записи добавляет в файл символ окончания строки
Рассмотрим запись в файл на примере:
exmpl_08
Здесь сначала мы считываем файл в переменную text, а затем записываем эту переменную в файл, а затем через объект StreamWriter записываем в новый файл.
Класс StreamWriter имеет несколько конструкторов. Здесь мы использовали один из них: new StreamWriter(writePath, false, System.Text.Encoding.Default). В качестве первого параметра передается путь к записываемому файлу. Второй параметр представляет булевую переменную, которая определяет, будет файл дозаписываться или перезаписываться. Если этот параметр равен true, то новые данные добавляются в конце к уже имеющимся данным. Если false, то файл перезаписывается. И если в первом случае файл перезаписывается, то во втором делается дозапись в конец файла.
Третий параметр указывает кодировку, в которой записывается файл.
Основные метода класса BinaryWriter
Close(): закрывает поток и освобождает ресурсы
Flush(): очищает буфер, дописывая из него оставшиеся данные в файл
Seek(): устанавливает позицию в потоке
Write(): записывает данные в поток
С чтением бинарных данных все просто: соответствующий метод считывает данные определенного типа и перемещает указатель на размер этого типа в байтах, например, значение типа int занимает 4 байта, поэтому BinaryReader считает 4 байта и переместит указать на эти 4 байта.
Посмотрим на реальной задаче применение этих классов. Попробуем с их помощью записывать и считывать из файла массив структур:
exmpl_09
Итак, у нас есть структура State с некоторым набором полей. В основной программе создаем массив структур и записываем с помощью BinaryWriter. Этот класс в качестве параметра в конструкторе принимает объект Stream, который создается вызовом File.Open(path, FileMode.OpenOrCreate).
Затем в цикле пробегаемся по массиву структур и записываем каждое поле структуры в поток. В том порядке, в каком эти значения полей записываются, в том порядке они и будут размещаться в файле.
Затем считываем из записанного файла. Конструктор класса BinaryReader также в качестве параметра принимает объект потока, только в данном случае устанавливаем в качестве режима FileMode.Open: new BinaryReader(File.Open(path, FileMode.Open))
В цикле while считываем данные. Чтобы узнать окончание потока, вызываем метод PeekChar(). Этот метод считывает следующий символ и возвращает его числовое представление. Если символ отсутствует, то метод возвращает -1, что будет означать, что мы достигли конца файла.
В цикле последовательно считываем значения поле структур в том же порядке, в каком они записывались.
Таким образом, классы BinaryWriter и BinaryReader очень удобны для работы с бинарными файлами, особенно когда нам известна структура этих файлов. В то же время для хранения и считывания более комплексных объектов, например, объектов классов, лучше подходит другое решение - сериализация.
Основные метода класса BinaryReader
Close(): закрывает поток и освобождает ресурсы
ReadBoolean(): считывает значение bool и перемещает указатель на один байт
ReadByte(): считывает один байт и перемещает указатель на один байт
ReadChar(): считывает значение char, то есть один символ, и перемещает указатель на столько байтов, сколько занимает символ в текущей кодировке
ReadDecimal(): считывает значение decimal и перемещает указатель на 16 байт
ReadDouble(): считывает значение double и перемещает указатель на 8 байт
ReadInt16(): считывает значение short и перемещает указатель на 2 байта
ReadInt32(): считывает значение int и перемещает указатель на 4 байта
ReadInt64(): считывает значение long и перемещает указатель на 8 байт
ReadSingle(): считывает значение float и перемещает указатель на 4 байта
ReadString(): считывает значение string. Каждая строка предваряется значением длины строки, которое представляет 7-битное целое число
Большинство задач в программировании так или иначе связаны с работой с файлами и каталогами. Нам может потребоваться прочитать текст из файла или наоборот произвести запись, удалить файл или целый каталог, не говоря уже о более комплексных задачах, как например, создание текстового редактора и других подобных задачах.
Фреймворк .NET предоставляет большие возможности по управлению и манипуляции файлами и каталогами, которые по большей части сосредоточены в пространстве имен System.IO. Классы, расположенные в этом пространстве имен (такие как Stream, StreamWriter, FileStream и др.), позволяют управлять файловым вводом-выводом.
Этот класс имеет статический метод GetDrives, который возвращает имена всех логических дисков компьютера. Также он предоставляет ряд полезных свойств:
AvailableFreeSpace: указывает на объем доступного свободного места на диске в байтах
DriveFormat: получает имя файловой системы
DriveType: представляет тип диска
IsReady: готов ли диск (например, DVD-диск может быть не вставлен в дисковод)
Name: получает имя диска
TotalFreeSpace: получает общий объем свободного места на диске в байтах
TotalSize: общий размер диска в байтах
VolumeLabel: получает или устанавливает метку тома
Помимо работы с файлами и каталогами, в .NET Framework предоставляется возможность читать информацию об указанном диске. Для этого предусмотрен класс DriveInfo. Этот класс умеет производить сканирование системы и составлять список всех доступных в ней дисков, а также детальные сведения о любом из них.
exmpl_12
exmpl_13