Работа с файлами. Побайтовое чтение/запись. Чтение текстовых данных
Любой ввод и вывод информации в .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); }