Класс FileStream

71

Класс FileStream предоставляет реализацию абстрактного члена Stream в манере, подходящей для потоковой работы с файлами. Это элементарный поток, и он может записывать или читать только один байт или массив байтов. Однако взаимодействовать с членами типа FileStream придется нечасто. Вместо этого, скорее всего, будут использоваться оболочки потоков, которые облегчают работу с текстовыми данными или типами .NET. Тем не менее, в целях иллюстрации полезно поэкспериментировать с возможностями синхронного чтения/записи типа FileStream.

Экземпляр FileStream применяется для чтения и записи данных в любой файл. Для создания экземпляра FileStream потребуется указать следующие фрагменты информации:

Первый из этих фрагментов информации обычно представлен в виде строки, содержащей полный путь к нужному файлу. Однако помимо строки существуют и дополнительные конструкторы, которые вместо строки принимают файловый дескриптор в стиле Windows API. Остальные три фрагмента информации представляются с помощью трех .NET-перечислений FileMode, FileAccess и FileShare. Значения этих перечислений описаны в таблице:

Перечисление Значение
FileMode Append, Create, CreateNew, Open, OpenOrCreate, Truncate
FileAccess Read, ReadWrite, Write
FileShare Delete, Inheritable, None, Read, ReadWrite, Write

Обратите внимание, что в случае FileMode, если запрашивается режим, не соответствующий существующему состоянию файла, может быть сгенерировано исключение. Значения Append, Open и Truncate будут приводить к генерации исключения, если файл не существует, а значение CreateNew — наоборот, если он уже существует. Значения Create и OpenOrCreate подходят в обоих сценариях, но Create приводит к удалению любого существующего файла и замене его новым, изначально пустым.

Перечисления FileAccess и FileShare являются битовыми флагами, поэтому их значения могут комбинироваться с помощью битовой операции "ИЛИ", т.е. |. Для создания FileStream доступно большое количество конструкторов. Три наиболее простейших из них работают так, как описано ниже:

// Создает файл с доступом для чтения и записи и позволяет
// другим потокам получать к нему доступ для чтения
FileStream fs = new FileStream(@"С:\C# Projects\Project.doc", FileMode.Create);

// Делает то же самое, что и предыдущий, но позволяет
// другим потокам получать доступ к файлу для записи
FileStream fs2 = new FileStream(@"С:\C# Projects\Project2.doc",
    FileMode.Create, FileAccess.Write);
    
// Делает то же самое, что и предыдущие конструкторы, но не
// позволяет другим потокам получать доступ к файлу до тех пор,
// пока fs3 остается открытым
FileStream fs3 = new FileStream(@"С:\C# Projects\Project3.doc",
    FileMode.Create, FileAccess.Write, FileShare.None);

Как видно в коде, перегруженные версии данных конструкторов способны подставлять стандартные значения FileAccess.ReadWrite и FileShare.Read на месте третьего и четвертого параметров в зависимости от значения FileMode. Можно также создавать файловый поток из экземпляра FileInfo.

После окончания работы поток нужно закрыть:

fs.Close();

Закрытие потока приводит к освобождению всех ассоциированных с ним ресурсов и позволяет другим приложениям запускать потоки для работы с тем же файлом. Кроме того, это действие приводит к очистке буфера. Между открытием и закрытием потока нужно производить собственно чтение и/или запись данных. В классе FileStream для этого предусмотрен набор методов.

Метод ReadByte() представляет собой самый простой способ для чтения данных. Он берет один байт из потока и приводит результат к типу int со значением в диапазоне от О до 255. В случае достижения конца потока он возвращает -1.

Если необходимо, чтобы за один раз читалось сразу множество байтов, можно вызывать метод Read(), который читает указанное количество байтов в массив. Метод Read() возвращает действительное количество прочитанных байтов; если возвращается значение О, значит, был достигнут конец потока. Ниже показан пример чтения данных в массив байтов по имени ByteArray:

int nBytesRead = fs.Read(ByteArray, 0, nBytes);

Во втором параметре метод Read() принимает значение смещения, которое позволяет указать, что массив должен заполняться, начиная не с первого, а с какого-то другого элемента. В третьем параметре можно указать, сколько байтов должно читаться в массив.

Для выполнения записи данных доступно два метода — WriteByte() и Write(). Метод WriteByte() позволяет записывать по одному байту в поток:

byte NextByte = 100;
fs.WriteByte(NextByte);

Помимо этих методов в FileStream есть и множество других методов и свойств, имеющих отношение к выполнению вспомогательных операций, таких как определение количества байтов в потоке, блокирование потока и очистка буфера. Для выполнения базовых операций чтения и записи эти дополнительные методы обычно применять не требуется; всю информацию о них можно найти в документации SDK.

В данной статье применение класса FileStream иллюстрируются на примере приложения BinaryFileReader, которое может считывать и отображать любой файл. Создайте в Visual Studio 2010 новый проект типа приложения WPF. Это приложение должно иметь один элемент меню, открывающий стандартное диалоговое окно OpenFileDialog для указания файла, и затем отображать содержимое этого файла в двоичном формате.

Поскольку считываться будут двоичные файлы, необходимо чтобы приложение было способно отображать непечатаемые символы. Для этого каждый байт файла будет отображаться отдельно, с выводом по 16 байтов в каждой строке многострочного текстового поля. В случае если байт представляет собой печатаемый ASCII-символ, будет отображаться этот символ, а если нет, то содержащее в байте значение будет отображаться в шестнадцатеричном формате. И в том и в другом случае отображаемый текст должен дополняться пробелами так, чтобы отображаемый байт занимал четыре столбца.

Используем следующую разметку и код:

<Window x:Class="BinaryFileReader.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="File" Click="MenuItem_Click"></MenuItem>
        </Menu>
        <TextBox Margin="0,5,0,5" x:Name="textFile" TextWrapping="Wrap" Grid.Row="1"
                 VerticalScrollBarVisibility="Auto"></TextBox>
    </Grid>
</Window>
using System;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;

namespace BinaryFileReader
{
    /// <summary>
    /// Логика взаимодействия для MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        // Диалоговое окно для отображения файлов
        private readonly OpenFileDialog myOpenFileDialog = new OpenFileDialog();

        // Путь к файлу
        private string pathFile;

        public MainWindow()
        {
            InitializeComponent();
            myOpenFileDialog.FileOk += OnOpenFileDialogOK;
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            myOpenFileDialog.ShowDialog();
        }

        private void OnOpenFileDialogOK(object sender, EventArgs e)
        {
            pathFile = myOpenFileDialog.FileName;
            DisplayFile();
        }

        void DisplayFile()
        {
            int nCols = 16;
            FileStream fs = new FileStream(pathFile, FileMode.Open, FileAccess.Read);
            long nBytesRead = fs.Length;
            if (nBytesRead > 65536 / 4)
                nBytesRead = 65536 / 4;
            int nLines = (int)(nBytesRead / nCols) + 1;
            string[] lines = new string[nLines];
            int nBytesToRead = 0;

            for (int i = 0; i < nLines; i++)
            {
                StringBuilder nextLine = new StringBuilder();
                nextLine.Capacity = 4 * nCols;

                for (int j = 0; j < nLines; j++)
                {
                    int nextByte = fs.ReadByte();
                    nBytesToRead++;
                    if (nextByte < 0 || nBytesToRead > 65536)
                        break;
                    char nextChar = (char)nextByte;
                    nextLine.Append(" x0"+string.Format("{0,1:X}", (int)nextChar));
                }
                lines[i] = nextLine.ToString();
            }
            fs.Close();
            string text = "";
            foreach (string l in lines)
                text += l;
            textFile.Text = text;
        }
    }
}
Чтение байтов из файла
Пройди тесты
Лучший чат для C# программистов