Работа с файлами. Побайтовое чтение/запись. Чтение текстовых данных
Любой ввод и вывод информации в .Net Framework включает в себя использование потоков.
Поток — абстрактное представление последовательного устройств, облегчающее последовательное хранение данных и доступ к ним (по одному байту в каждый конкретный момент времени). В качестве такого устройства могут выступать расположенный на диске файл, принтер, область памяти, любой другой объект допускающий последовательное считывание и запись информации).
В пространстве имен System.IO хранятся классы, предназначенные для считывания и записи данных в файлы. Классы:
File – содержит статические методы для манипуляции файлами (создание, копирование, удаление); Directory – содержит статические методы для манипуляции директориями;
Path – статических класс, «путь»; FileInfo – не обладает статическими методами, соответствует физическому фалу, во многом дублирует функциональность File;
FileInfo aFile = new FileInfo("d:\log.txt");
if (aFile.Exists == false) { aFile.Create(); }
aFile.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden;
// aFile.Attributes = aFile.Attributes &~FileAttributes.ReadOnly; // убрать атрибут
FileStream – представляет поток, указывающий на файл или местоположение в сети. Представляет файл для считывания/записи, оперирует байтами и массивом байтов, в то время как Stream оперирует символьными данными.
FileStream fs = new FileStream("d:\log.txt", FileMode.OpenOrCreate, FileAccess.Read);
>> enum FileMode {
Append, // открывает (если существует), переводит указатель в конец, или создает новый файл. Может использоваться только совместно с FileAccess.Write
Create, // создает (если существует – заменяет)
CreateNew, // создает (если существует – генерируется исключение)
Open, // открывает (если не существует – генерируется исключение)
OpenOrCreate, // если существует открывает, иначе создает новый
Truncate // открывает существ. файл,но всю ифн. внутри затирает (если не существует – исключение)
}
>> enum FileAccess { Rad, Write, ReadWrite }
FileStream fs = File.OpenRead("d:\log.txt"); // открывает "только на чтение"
FileStream fs = File.OpenWrite("d:\log.txt"); // открывает для записи
Класс FileStream поддерживает внутренний указатель файла, ссылающийся на то место в файле, в котором будет производиться очередная операция чтения/записи. Метод Seek() позволяет осуществить поиск конкретной позиции в файле (байтовой).
public long Seek(long offset, SeekOrigin origin);
// origin = { Begin, End, Current }
// offset – на сколько вперед в байтах должен быть передвинут указатель
// origin – с какой точки веси отсчет
fs.Seek(-5, SeekOrigin.End); // переходит на 5й с конца байт файла
При чтении и записи в файл, происходит изменение позиции указателя (при считывании на 1б)
// побайтовое чтение из файла с отступом в 55 байт
byte[] byData = new byte[100]; // массив байтов
char[] charData = new char[100]; // масив символов
try
{ // файловый поток, открывает файл (при отсутсвии создает) только для чтения
FileStream fs = new FileStream("d:\log.txt", FileMode.OpenOrCreate, FileAccess.Read);
if (fs.CanSeek == true) // если можно производить поиск
{
fs.Seek(55, SeekOrigin.Begin); // делаем отступ на 55 байт с начала файла
// чтение данных и запись в масив байтов, со двигом в мас. 0, и длинной в 100 байт
fs.Read(byData, 0, 100);
}
fs.Dispose(); // освобождаем ресурсы
}
catch (IOException err)
{
MessageBox.Show(err.Message); return;
}
Decoder d = Encoding.UTF8.GetDecoder(); // декодирует в кодировку UTF8 (Unicode)
d.GetChars(byData, 0, byData.Length, charData, 0); // преобразовывет байты в символы
string str = new string(charData); // строим строку
MessageBox.Show(str);
}
// записываем в файл побайтно начиная с позиции 55 набор байтов
char[] charArr = "sauron918".ToCharArray();
byte[] byteArr = new byte[500];
try
{
FileStream fs = new FileStream("d:\log.txt", FileMode.OpenOrCreate, FileAccess.Write);
fs.Seek(55, SeekOrigin.Begin);
Encoder enc = Encoding.UTF8.GetEncoder();
enc.GetBytes(charArr, 0, charArr.Length, byteArr, 0, true); // перекодирование
fs.Write(byteArr, 0, byteArr.Length); // запись массива байт
fs.Dispose(); // освобождаем ресурсы
}
catch (Exception err)
{
MessageBox.Show(err.Message); return;
}
Классы Stream позволяют осуществлять последовательный доступ к файлам, и в них не предусмотрена возможность работы с указателем.
StreamWriter – позволяет осуществлять запись в файл символов и строк и самостоятельно выполняет все необходимые преобразования.
StreamWriter sw = new StreamWriter(fs);
StreamWriter sw = new StreamWriter("d:\log.txt", true);
// true - добавлять инф. или создать новый
--
FileStream fs = new FileStream("d:\log.txt", FileMode.OpenOrCreate, FileAccess.Write);
StreamWriter sw = new StreamWriter(fs);
sw.WriteLine("hello world");
sw.Write("this is ");
sw.Close();
StreamReader – осуществляет чтение символьных данных из потока и их преобразование.
StreamReader sr = new StreamReader("d:\log.txt", Encoding.UTF8);
while (sr.Peek() != -1)
{
Line = sr.ReadLine(); // Line = sr.ReadToEnd();
MessageBox.Show(Line);
}
sr.Dispose(); // sr.Close();
--
string path = @"c:tempMyTest.txt";
try
{
if (File.Exists(path)) { File.Delete(path); }
using (StreamWriter sw = new StreamWriter(path))
{
sw.WriteLine("is some text");
}
using (StreamReader sr = new StreamReader(path))
{
while (sr.Peek() != -1) // проверяет следующий символ, но не считывает
{
Console.WriteLine(sr.ReadLine());
}
}
}
catch (Exception e) { Console.WriteLine("The process failed: {0}", e.ToString()); }
--
// чтение файлов с разделителями
string Line;
string[] strArr;
char[] charArr = new char[] { ' ' };
try
{
FileStream fs = new FileStream("d:\log.txt", FileMode.Open);
StreamReader sr = new StreamReader(fs, Encoding.UTF8);
while (sr.EndOfStream != true) // framework 2.0
{
Line = sr.ReadLine();
strArr = Line.Split(charArr);
for (int i = 0; i < strArr.Length; i++)
{
MessageBox.Show(strArr[i].Trim());
}
}
sr.Close();
}
catch(Exception ex) { MessageBox.Show(ex.Message); }
FileStreamWatcher – используется для слежения за состоянием файловой системы (файлов и директорий) и генерирует события в моменты, когда изменяется их местоположение. Сначала нужно задать значения свойств, определив, где следует осуществлять контроль, что нужно контролировать и когда следует генерировать события. Свойства:
Path – путь к файлу/директории, подлежащей контрою.
NotifyFilter – сочетание значений перечисляемого типа NotifyFilters, которое позволяет определить за наступлением каких именно событий для данных файлов следует наблюдать. { Attributes, CreationTime, DirectoryName, FileName, LastAccess, LastWrite, Security, Size }. Допускается использование различных сочетаний этих значений посредством оператора | или &.
Filter – фильтр, определяющий какие именно файлы подлежат контролю, например, *.txt
EnableRaisingEvents – после задания всех свойст необходимо присвоить значение true, что будет означать начало наблюдения.
…
this.watcher = new System.IO.FileSystemWatcher();
// объект наблюдение за файловой системой
private System.IO.FileSystemWatcher watcher;
…
public Form1()
{
InitializeComponent();
DirectoryInfo di = new DirectoryInfo("D:\Source"); // директория для мониторинга
if (di.Exists == false) di.Create();
watcher.Deleted += new FileSystemEventHandler(watcher_Deleted);
watcher.Renamed += new RenamedEventHandler(watcher_Renamed);
watcher.Changed += new FileSystemEventHandler(watcher_Changed);
watcher.Created += new FileSystemEventHandler(watcher_Created);
}
void watcher_Created(object sender, FileSystemEventArgs e)
{
try
{
StreamWriter sw = new StreamWriter("d:\log.txt", true);
sw.WriteLine("Файл {0} создан", e.FullPath);
sw.Close();
lbWatch.Text = "Файл создан";
}
catch (IOException) { lbWatch.Text = "Ошибка записи в лог"; }
}
void watcher_Changed(object sender, FileSystemEventArgs e)
{
try
{
// открыть ф. для дополнения (true), елси нет - создать
StreamWriter sw = new StreamWriter("d:\log.txt", true);
sw.WriteLine("Файл: {0} {1}", e.FullPath, e.ChangeType.ToString());
sw.Close();
lbWatch.Text = "Записыю измения в лог";
}
catch (IOException) {lbWatch.Text = "Ошибка записи в лог";}
}
void watcher_Renamed(object sender, RenamedEventArgs e)
{
try
{
StreamWriter sw = new StreamWriter("d:\log.txt", true);
sw.WriteLine("Файл переименован из {0} в {1}", e.OldName, e.FullPath);
sw.Close();
lbWatch.Text = "Файл переименован";
}
catch (IOException) {lbWatch.Text = "Ошибка записи в лог";}
}
void watcher_Deleted(object sender, FileSystemEventArgs e)
{
try
{
StreamWriter sw = new StreamWriter("d:\log.txt", true);
sw.WriteLine("Файл {0} был удален", e.FullPath);
sw.Close();
lbWatch.Text = "Файл удалён";
}
catch (IOException) { lbWatch.Text = "Ошибка записи в лог"; }
}
private void btnWatch_Click(object sender, EventArgs e)
{
watcher.Path = Path.GetDirectoryName(txtLocation.Text);
watcher.Filter = Path.GetFileName(txtLocation.Text); // "*.*
// уведомлять об изм. времени последней записи, имени файла, размера файла
watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size;
lbWatch.Text = "Наблюдаю: " + txtLocation.Text;
watcher.EnableRaisingEvents = true; // начало наблюдения
}
private void btnBrowse_Click(object sender, EventArgs e)
{
if (FileDialog.ShowDialog() != DialogResult.Cancel)
{
txtLocation.Text = FileDialog.FileName;
btnWatch.Enabled = true;
}
}
...
string str;
try
{
using (StreamReader sr = new StreamReader("d:\log.txt"))
{
while((str = sr.ReadLine()) != null)
{
MessageBox.Show(str);
}
}
}
catch (IOException err) { MessageBox.Show(err.Message); }
Асинхронный доступ к файлам
Приложение может выполнять какие-то действия параллельно с процессом ввода/вывода файла, вместо ожидания окончания этого процесса. Процесс считываня файла начинается с вызова метода BeginRead(), методу передаются параметры аналогичные методу Read() + дополнительные, необходимые для осуществления асинхронного процесса.
byte[] byteData = new byte[100];
char[] charData = new char[100];
try
{
FileStream fs = new FileStream("d:\log.txt", FileMode.Open);
fs.Seek(0, SeekOrigin.Begin); // на начало файла
// начало процедуры асинхронного чтения из файла, первые 3 параметра аналогичные Read()
// 4й пар.-делегат на метод, к.будет вызван по завершению чтения
// 5й пар.-заданный пользов. объект состояния, предназначенный для передачи некой
// строки или данных, позволяющих идентифицир. данную асинхронную операцию
IAsyncResult asResult = fs.BeginRead(byteData, 0, byteData.Length, null, null);
// выполнение друхиг действий паралельно с чтение данных
while (!asResult.IsCompleted) // хранит инф. о сотоянии процесса
{
// другие действия только здесь...
MessageBox.Show("reading from file...");
}
// завершение чтения. передается объект IAsyncResult, возвращенный методом Begin()
fs.EndRead(asResult);
// обработка данных, без нее завершить процесс безсмысленно
Decoder d = Encoding.ASCII.GetDecoder();
d.GetChars(byteData, 0, byteData.Length, charData, 0);
MessageBox.Show(new string(charData));
fs.Close();
}
catch (Exception err)
{
MessageBox.Show(err.Message);
}
Другой, более совершенный, способ осуществления доступа к файлам в асинхронном режиме включает использование метода, возвращающего сообщение (о том что операция завершена).
public class AsyncRead
{
byte[] byteData;
char[] charData;
public AsyncRead()
{
byteData = new byte[1000];
charData = new char[1000];
try
{
FileStream fs = new FileStream("d:\log.txt", FileMode.Open);
fs.Seek(0, SeekOrigin.Begin); // на начало файла
// делегат указатель на функцию, которая будет обрабатываться
AsyncCallback cb = new AsyncCallback(this.HandleRead);
IAsyncResult aResult = fs.BeginRead(byteData, 0, byteData.Length, cb, "read log.txt");
}
catch (IOException err) { MessageBox.Show(err.Message); }
}
// действия которые будут выполняться по завершению обработки
public void HandleRead(IAsyncResult ar)
{
Decoder d = Encoding.ASCII.GetDecoder();
d.GetChars(byteData, 0, byteData.Length, charData, 0);
MessageBox.Show(new string(charData));
}
…
private void button2_Click(object sender, EventArgs e)
{
AsyncRead ar = new AsyncRead();
// продолжаем основные действия
for (int i = 0; i < 50; i++) { MessageBox.Show(i.ToString()); }
}
Создается экземпляр класса AsyncReader, а заме продолжается выполнение – вывод порядковых чисел на консоль; теперь этому методу не приходиться отслеживать процедуру считывания, и он может выполнять какие-либо другие действия, совершенно от этой процедуры не зависящие. Метод HandleRead вызывается системой, когда завершается процедура считывания файла. Это позволяет приложению продолжать обработку какой-либо иной информации, пока выполняется относительно медленная процедура считывания файла.
Пример:
namespace WindowsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.btnOK.Enabled = false;
this.LoadOccupation();
this.txtName.Tag = false;
this.txtAge.Tag = false;
this.txtAddress.Tag = false;
this.txtName.Validating += new CancelEventHandler(this.txtBoxEmpty_Validating);
this.txtAddress.Validating += new CancelEventHandler(this.txtBoxEmpty_Validating);
this.txtAge.Validating += new CancelEventHandler(this.txtBoxEmpty_Validating);
this.txtAge.KeyPress += new KeyPressEventHandler(txtAge_KeyPress);
this.txtName.TextChanged += new EventHandler(txtBox_TextChanged);
this.txtAddress.TextChanged += new EventHandler(txtBox_TextChanged);
this.txtAge.TextChanged += new EventHandler(txtBox_TextChanged);
}
private void btnOK_Click(object sender, EventArgs e)
{
string output;
output = "Имя: " + this.txtName.Text + "rn";
output += "Адрес: " + this.txtAddress.Text + "rn";
output += "Профессия: " + this.cmbOccupation.Text + "rn";
output += "Пол: " + (string)(this.rdoMale.Checked ? "Мужской" : "Женский") + "rn";
output += "Возраст: " + this.txtAge.Text;
this.txtOutput.Text = output;
}
// чтение из файла
private void LoadOccupation()
{
try
{
// создание объекта StreamReader
System.IO.StreamReader sr = new System.IO.StreamReader("occupation.txt");
string input;
do
{
input = sr.ReadLine(); // построчное чтение
if (input != "") // пропуск пустых строк
this.cmbOccupation.Items.Add(input);
} while (sr.Peek() != -1); // -1 возвращается когда достигнут конец потока
sr.Close();
}
catch (Exception)
{
MessageBox.Show("Файл не найден");
}
}
// запись в файл
private void SaveOccupation()
{
try
{
System.IO.StreamWriter sw = new System.IO.StreamWriter("occupation.txt");
foreach (string item in this.cmbOccupation.Items)
{
sw.WriteLine(item); // запись
}
sw.Flush(); // очистка буфера
sw.Close();
}
catch (Exception)
{
MessageBox.Show("Файл не найден");
}
}
private void ValidateAll()
{
this.btnOK.Enabled = ((bool)(this.txtName.Tag) &&
(bool)(this.txtAddress.Tag) &&
(bool)(this.txtAge.Tag));
}
private void cmbOccupation_KeyDown(object sender, KeyEventArgs e)
{
// если Text отсутствует в семействе Items то добавляем его
int index = 0;
ComboBox cb = (ComboBox)sender;
if (e.KeyCode == Keys.Enter)
{
// осуществ. поиск строки и не является чувствительным к регистру
index = cb.FindStringExact(cb.Text);
if (index < 0)
cb.Items.Add(cb.Text);
else
cb.SelectedIndex = index;
// указывает на то что событие KeyDown нами обработано
e.Handled = true;
}
}
}
}
…
protected override void Dispose(bool disposing)
{
// сохранение элементов
SaveOccupation();
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

