определенного источника данных, мы облегчаем повторное применения этого кода (через наследование) в различных обстоятельствах. Например, упомянутые выше классы StringReader
и StringWriter
являются частью того же дерева наследования, что и два класса, используемых для чтения и записи текстовых файлов, — StreamReader
и StreamWriter
. Классы почти наверняка неявно задействуют значительный объем общего кода.
Реальная иерархия связанных с потоком классов в пространстве имен System.IO
выглядит следующим образом:
Что касается чтения из файлов или записи в файлы, то мы будем связаны в основном со следующими классами:
□ FileStream
. Этот класс предназначен для чтения и записи двоичных данных в произвольный двоичный файл, однако при желании можно использовать его для чтения и записи в любой файл.
□ StreamReader
и StreamWriter
. Эти классы специально предназначены для чтения и записи в текстовые файлы.
Упомянем также другие классы, которые могут оказаться полезными, хотя они и не будут представлены в приводимых примерах. Если вы хотите использовать эти классы, обратитесь к документации MSDN, чтобы получить подробности об их работе.
BinaryReader
и BinaryWriter
. Эти классы в действительности сами не реализуют потоки, но они могут обеспечить оболочки вокруг других потоковых объектов. BinaryReader
и BinaryWriter
поддерживают дополнительное форматирование двоичных данных, что позволяет напрямую читать или записывать содержимое переменных C# в соответствующий поток. Проще всего считать, что BinaryReader
и BinaryWriter
находятся между потоком и кодом приложения, обеспечивая дополнительное форматирование:
Различие между использованием этих классов и непосредственным использованием описанных ниже потоковых объектов состоит в том, что базовый поток работает с байтами. Например, пусть часть процесса сохранения некоторого документа состоит в записи содержимого переменной типа long
в двоичный файл. Каждая переменная типа long
занимает 8 байтов, если используется плоский обыкновенный двоичный поток, необходимо будет явно записывать каждые эти 8 байтов памяти. В коде C# это будет означать, что необходимо явно выполнять некоторые битовые операции для извлечения каждых 8 байтов из значения long
. Используя экземпляр BinaryWriter
, можно инкапсулировать всю операцию в перегруженный метод BinaryWriter.Write()
, который получает long
в качестве параметра и который будет помещать эти 8 байтов в поток (и следовательно, если поток направлен в файл, то в файл). Соответствующий метод BinaryReader.Read()
будет извлекать 8 байтов из потока и восстанавливать значение long
.
BufferedStream
. По соображениям производительности при чтении из файла или при записи в файл вывод буферизуется. Это означает, что если программа запрашивает следующие 2 байта файлового потока и поток передает запрос Windows, то Windows не станет соединяться с файловой системой и затем искать и считывать файл с диска, для того чтобы получить 2 байта. Вместо этого произойдет извлечение большого блока файла за один раз и сохранение этого блока в области памяти, называемой буфером. Последующие запросы данных из потока будут удовлетворяться из буфера, пока он не будет исчерпан, и тогда Windows извлечет другой блок данных из файла. Запись в файлы работает таким же образом. Для файлов это делается автоматически операционной системой, но возможен случай, когда придется написать потоковый класс для чтения из некоторого другого устройства, которое не буферизуется. В таком случае можно вывести этот класс из BufferedStream
, который сам реализует буфер (BufferedStream
не создан, однако, для ситуаций, когда приложение часто чередует операции чтения и записи данных).
Чтение и запись двоичных файлов
Чтение и запись двоичных файлов делается обычно с помощью класса FileStream
.
Класс FileStream
Экземпляр FileStream
используется для чтения или записи данных файла. Чтобы создать FileStream
, необходимо иметь данные четырех видов:
□ Файл для доступа.
□ Режим, который указывает, как необходимо открыть файл. Например, собираетесь ли вы создать новый файл или открыть существующий файл, и, если открывается существующий файл, должна ли какая-либо операция записи интерпретироваться как перезапись содержимого файла или как добавление к файлу.
□ Доступ, указывающий, как будет осуществляться доступ к файлу, будет ли выполняться чтение или запись в файл или и то и другое.
□ Общий доступ. Другими словами, будет ли осуществляться исключительный доступ к файлу, или желательно, чтобы другие потоки могли одновременно получать доступ к этому файлу. Если так, то должны ли другие потоки иметь доступ для чтения файла, записи в него или для того и другого.
Первый из этих видов данных представлен обычно строкой, которая содержит полное имя пути доступа файла, и в этой главе будут рассматриваться только те конструкторы, которые требуют строку. Помимо этих конструкторов, существуют и некоторые другие, которые получают дескриптор файла Windows в стиле старого Windows API. Остальные три вида данных представлены тремя перечислениями .NET, называемыми соответственно FileMode
, FileAccess
и FileShare
. Значения этих перечислений должны быть понятны из названий.
Перечисление | Значения |
---|---|
FileMode (режим файла) | Append (добавить), Create (создать), CreateNew (создать новый), Open (открыть), OpenOrCreate (открыть или создать), Truncate (обрезать) |
FileAccess (доступ к файлу) | Read (чтение), ReadWrite (чтение-запись), Write (запись) |