Класс FileStream
71C# и .NET --- Многопоточность и файлы --- Класс FileStream
Класс 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;
}
}
}
