C# → Запись и чтение из файла

Работа с файлами. Побайтовое чтение/запись. Чтение текстовых данных

Любой ввод и вывод информации в .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 вызывается системой, когда завершается процедура считывания файла. Это позволяет приложению продолжать обработку какой-либо иной информации, пока выполняется относительно медленная процедура считывания файла.

Пример:

read
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);
        }
  • Программист 80лвл

    not bad…

  • Витьок

    поддерживаю вас, сударь….

  • редоК

    Читать трудно, все тесно и сумбурно написано.

    Например, данный пример из статьи:

    StreamWriter – позволяет осуществлять запись в файл символов и строк и самостоятельно выполняет все необходимые преобразования.

    StreamWriter sw = new StreamWriter(fs);
    StreamWriter sw = new StreamWriter(«d:loh.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();

    просто смешной. Во-первых — к чему три строки создания StreamWriter’а под одним именем, два различных способа записи тесно воткнуты друг к другу без каких либо комментариев. Да и («d:loh.txt», true) — опечатка, но все же! Если в таком простом примере столько путаницы, дальше читать желание отпадает.

  • Mikhail Teterkin

    Вроде бы ошибочка Ошибочка
    enum FileAccess { Rad, Write, ReadWrite } \Rad -> Read