Урок 14. Работа с файлами

Лабораторные работы си шарп и задания по теме «Язык программирования c sharp: файлы»

Теория

    Классификация файлов

    • Компонентный тип:
    • текстовые файлы (состоит из строк в некоторой кодировке)
    • бинарные файлы
    • Тип доступа к файлу:
    • Произвольный доступ: время доступа = Θ(1), компоненты данных имеют одинаковый размер
      (байты в простейшем случае)
    • Последовательный доступ: компоненты данных имеют различный размер, для поиска i-го компонента необходимо считать предыдущий i-1 компонент (пример: текстовые файлы)
    • Типы операций с файлами:
    • только для чтения
    • только для записи
    • чтение и запись

    Операции с файлами

    с закрытыми файлами:
    открыть
    проверить существует ли
    удалить
    скопировать
    переименовать
    переместить
    С уже открытыми файлами:
    закрыть
    записать информацию
    читать информацию
    проверить, достигнут ли конец файла
    поиск n-й позиции

    Классы и пространства имен .NET

      Пространства имен:

    • using System.IO;
    • using System.Text;
    • В платформе .net есть пространство имен System.IO, именно там расположено множество классов для работы с файлами и каталогами.

      Классы:

    • File – статический класс, операции с закрытыми файлами
    • FileMode – режим открытия файла
    • FileStream
    • StreamReader/StreamWriter – потоки для чтения-записи в текстовые файлы
    • BinaryReader/BinaryWriter — потоки для чтения-записи в двоичные файлы
    • Encoding – кодировки файлов
    • Классы File и FileInfo оба предоставляют методы для создания, копирования, удаления, перемещения и открытия файлов. Они имеют очень похожие интерфейсы, разница лишь в том, что FileInfo обеспечивает методы экземпляра, тогда как File обеспечивает методы static.
    • Разница в том, что если у вас будет небольшое количество операций, удобнее обращаться к статическим методам класса File. Если у вас будет много операций, более эффективно создать класс FileInfo и получить доступ ко всем его методам экземпляра.
    • Класс Directory предоставляет статические методы, в то время как DirectoryInfo предоставляет методы экземпляра.
    • У нас также есть класс Path, который предоставляет методы для работы со строкой, содержащей информацию о пути к файлу или каталогу.
    FileMode тип перечисления
  • FileMode.Create
  • FileMode.Open
  • FileMode.OpenOrCreate
  • FileMode.Append
  • Основные методы класса File

      Operations with closed files:

      using System.IO;
       
      // File.Exists("a.dat")
      Console.WriteLine(File.Exists("a.dat")); // true или false
      // File.Exists("d:\\a.dat")
      Console.WriteLine(File.Exists("d:\\a.dat")); // true или false
      // File.Exists(@"d:\a.dat")
      Console.WriteLine(File.Exists(@"d:\a.dat"); // true или false
       
      File.Delete("a.dat");
      File.Copy("d:\\a.dat", "d:\\b.dat"); // будет создан файл b.dat, если он существует - возникнет исключение
      File.Copy("a.dat", @"d:\a.dat");
      File.Move("a.dat", @"..\a.dat");

    Конструкция try .. catch

    try
    {
      // код с возможным исключением 
    }
    catch (Exception e)
    {
      // для обработки исключения
    }

    Пример:

    try
      {
        var path = @"d:\a.dat";
        var fs = new FileStream(path, FileMode.Open);
        ...
      }
    catch (FileNotFoundException e)
      {
        Console.WriteLine($"Файл с именем {e.FileName} не найден");
      }
    catch (FileLoadException e)
      {
         Console.WriteLine($"К файлу с именем {e.FileName} невозможно получить доступ");
      }

    ИнструкцияUsing

    Пример работы с двоичными файлами:

    using System.IO;
    // ...
     
    string path = @"Lesson14.dat";
    // данные в файле: 1 2 3 4 
    var fs = new FileStream(path, FileMode.Open);
    var br = new BinaryReader(fs);
    var a=br.ReadInt32();
    Console.WriteLine(a); // 1
    var b = br.ReadInt32(); 
    Console.WriteLine(b); // 2
    var c = br.ReadInt32();
    Console.WriteLine(c); // 3
     
    fs.Close();
    • Инструкция Using обеспечивает автоматический вызов метода Close() при работе с классом FileStream.

    Example:

    var path = @"d:\a.dat";
    using (var fs = new FileStream(path, FileMode.Create))
    {
      fs.WriteByte(1);
      fs.WriteByte(2);
      fs.WriteByte(3);
    }
    using (var fs = new FileStream(path, FileMode.Open))
    {
      Console.WriteLine(fs.ReadByte());
      Console.WriteLine(fs.ReadByte());
      Console.WriteLine(fs.ReadByte());
    }

Задания и лабораторные работы

Использование режима для создания файла и работы с ним без класса BinaryReader

Задание 0:

Выполнить: Create a file with a name myFile.txt on your root folder d or c (@"D:\myFile.txt"). Open created file and write there some entered string (don’t forget to convert it into a byte array -> Encoding.Default.GetBytes()). Then, read the file’s data and output it to the console window (by convering data again into string -> Encoding.Default.GetString(...)).
Создайте файл с именем myFile.txt в вашей корневой папке d или c (@"D:\myFile.txt"). Откройте созданный файл и запишите туда некоторую введенную строку (не забудьте преобразовать ее в байтовый массив -> Encoding.Default.GetBytes()). Затем считайте данные файла и выводите их в окно консоли (путем повторного преобразования данных в строку -> Encoding.Default.GetString(...)).
    
Примерный вывод:

Введите строку:
Hello, world!
Строка из файла:
Hello, world!

[Название проекта: Lesson_14Task0, Название файла L14Task0.cs]

Обработка ошибок при работе с файлами. Оператор Try. Считывание данных из файла

Лабораторная работа 1. Ошибка доступа на чтение к несуществующему файлу. Считывание данных из файла
  
Выполнить:

  • Создайте программу для вывода содержимого файла в окно консоли (числа могут быть выведены через пробелы в строку). Вы должны использовать класс BinaryReader.
  • Скачайте файл L01.dat и поместите его в свою рабочую папку (~/Lesson_14 Lab1/bin/Debug). В файле записано 15 целых чисел..
  • Вы должны использовать инструкцию using при работе с уже открытым файлом. Тогда, независимо от успеха работы с открытым файлом, он всегда будет закрыт.
  • using (var fs = new FileStream(path, FileMode.Create))
    {
      ...
    }
  • Используйте код для обработки исключений:
    • Чтобы предотвратить «сбой» программы и не напугать пользователя, добавьте обработку исключений — блок try..catch. Оператор using должен находиться внутри блока try..catch.
    • ...
      try
      {
           ...
      }
      catch (IOException e)
      { 
            Console.WriteLine($"Ошибка чтения из файла: {e.Message}");
      }
  • Измените имя файла в программном коде на несуществующее. Запустите приложение и проверьте обработку исключения. Снова измените имя на правильное имя.
  • Здесь нет необходимости создавать методы.

Примерный вывод:

Данные из файла:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

+++++++++++++
Данные из файла:
Ошибка чтения из: Файл 'C:\Users\User\Documents\Visual Studio 2015\Projects\L14_Lab1\bin\Debug\L02.dat' не найден

 
[Название проекта: Lesson_14Lab1, название файла L14Lab1.cs]

✍ Алгоритм:

  1. Создайте новый проект с именем файла, как указано в задании.
  2. Загрузите файл L01.dat и поместите его в свою рабочую папку (~/Lesson_14Lab1/bin/Debug).
  3. Чтобы избежать необходимости постоянно упоминать класс Console, мы можем включить его объявление прямо в начале окна редактора:
  4. ...
    using static System.Console;
    ...
    
  5. Ниже этого кода подключите пространство имен System.IO — для работы с файлами:
  6. using System.IO;
    ...
    
  7. В функции Main объявите переменную для хранения пути к файлу. Лучше использовать дословную строку (символ @):
  8. string path = @"L01.dat";
    
  9. Чтобы прочитать что-то из файла, сначала нужно открыть файл. Чтобы открыть файл, требуется указать путь к файлу в системе и режим.
  10. Итак, нам нужно инициализировать новый экземпляр класса FileStream с указанным путем и режимом. Будем использовать режим open (для чтения из файла), потому что нам не нужно ничего записывать в файл. Переменная fs будет хранить входной поток:
  11. using (var fs = new FileStream(path, FileMode.Open))
    
    Класс FileStream предоставляет поток (последовательность байтов) для ввода/считывания из файла; поддерживает как синхронные, так и асинхронные операции чтения и записи.
  12. После этого нам нужно создать переменную (br) — экземпляр класса BinaryReader, чтобы иметь возможность считывать данные из файла в виде двоичных значений:
  13. ...
    using (var br = new BinaryReader(fs))
    
  14. Чтобы не возникало ошибки, если файл не существует или если имя файла написано неверно, мы будем использовать блок try...catch. Поместите добавленные инструкции using в блок try:
  15. ...
    try
              {
                    using (var fs = new FileStream(path, FileMode.Open))
                    using (var br = new BinaryReader(fs))
              }
    
  16. Кроме того, внутри блока try мы собираемся считывать байты или символы из файла и выводить их в консоль. Для этой цели будем использовать метод PeekChar класса BinaryReader:
  17. // в блоке try, после использования инструкций using:
    while (br.PeekChar() != -1)
           {
               var x = br.ReadInt32();
               Write($"{x} ");
            }
    
    BinaryReader класс считывает примитивные типы данных как двоичные значения в определенной кодировке.
    PeekChar метод возвращает следующий доступный символ или -1, если больше нет доступных символов или поток не поддерживает поиск.
  18. В блоке catch мы собираемся распечатать сообщение в случае возникновения какой-либо ошибки, например, в случае неправильного имени файла:
  19. // under try block:
    catch (IOException e)
                    {
                        WriteLine($"Ошибка чтения из файла: {e.Message}");
                    }
    
    Класс IOException хранит исключение, которое генерируется при возникновении ошибки ввода-вывода.
    Message — это атрибут класса Exception, который возвращает сообщение об ошибке, объясняющее причину исключения, или пустую строку.
  20. В конце программного кода поместите метод ReadKey, чтобы не закрывать окно консоли при запуске программы в режиме отладки:
  21. // после закрывающей скобки блока catch
    ReadKey();
    
    Метод ReadKey ожидает ввода или нажатия клавиши клавиатуры. Нажатая клавиша отображается в окне консоли.
  22. Нажмите клавишу F5, чтобы запустить программу в режиме отладки и проверить выходные данные.
  23. Чтобы увидеть результат при возникновении ошибки, измените имя файла, например:
  24. string path = @"L02.dat";
    
  25. Нажмите клавишу F5, чтобы запустить программу в режиме отладки и проверить выходные данные.
  26. Верните прежнее название файла
  27. Загрузите файл .cs на проверку.
Задание 1:

Выполнить: Скопируйте файл L01.dat для предыдущей лабораторной работы и вставьте его в папку текущего задания, пусть к файлу: (~/Lesson_14Task1/bin/Debug).

1. Создайте программу для вывода чисел в квадрате из файла в окно консоли (числа могут быть выведены через пробел в пределах одной строки), а также для вычисления количества четных цифр среди чисел. Вы должны использовать класс BinaryReader. Создайте программу, используя блок try..catch.

Указание: Здесь нет необходимости создавать методы.
     

Примерный вывод:

Квадраты чисел:
1 4 9 16 25 36 49 64 81 100 121 144 169 196 225
Количество четных: 7
+++++++++++++++
Ошибка чтения из файла: Файл 'c:\users\user\documents\visual studio 2015\Projects\L_14Task1\bin\Debug\L02.dat' не найден.

[Название проекта: Lesson_14Task1, название файла L14Task1.cs]

Лабораторная работа 2. Несколько блоков try..catch. Считывание из файла
  
Выполнить:

  • Загрузите файл в папку с проектом L01.dat. В файле записаны целые числа. Выведите эти числа в окно консоли по N чисел в каждой строке (N вводится (N > 0) и имеет значение по умолчанию = 10). Необходимо использовать класс BinaryReader. В случае если файл пустой выведите сообщение «пустой файл».
  • Может быть несколько блоков try..catch, и каждый из них может обрабатывать разные ошибки. Напишите свой код так, чтобы один блок try..catch обрабатывал ошибки, возникающие при открытии файла, а другой блок обрабатывал ошибки, возникающие при работе с уже открытым файлом.
    • Тогда структура программы должна быть следующей:
    • ...
      try 
      {
          using ( ... )
          using ( ... )
          { 
              try 
              {
                  // работа с открытым файлом...
              }
              catch 
              {     
                  // обработка ошибки при работе с уже открытым файлом
              }
         }
      catch 
      {
          // обработка ошибки, связанной с открытием файла
      }
  • Создайте метод с двумя параметрами для выполнения задачи. Сигнатура метода:
  • static void PrintFileInRows(string path, int N=10)
  • Проверьте, правильно ли работают блоки try..catch. Смоделируйте ошибку при работе с уже открытым файлом и ошибку с именем файла. После проверки верните предыдущий (правильный) код и закомментируйте неверный код.

Примерный вывод:

Введите N:
3
1 2 3
4 5 6
7 8 9
10 11 12
13 14 15
+++++++++++++
Введите N:
3
Ошибка чтения из файла: Файл 'c:\users\user\documents\visual studio 2015\Projects\L_14Lab2\bin\Debug\L02.dat' не найден.
+++++++++++++
Введите N:
3
1 2 3
4 5 6
7 8 9
10 11 12
13 14 15 
Ошибка работы с данными файла: Чтение после конца потока невозможно.

 
[Название проекта: Lesson_14Lab2, название файла L14Lab2.cs]

✍ Алгоритм:

  1. Создайте новый проект с именем и именем файла, как указано в задаче.
  2. Загрузите файл L01.dat и разместите его в рабочей папке проекта (~/Lesson_14Lab2/bin/Debug).
  3. Чтобы не печатать постоянно класс Console, мы можем включить его объявление прямо в начале окна редактора:
  4. ...
    using static System.Console;
    ...
    
  5. Подключите пространство имен <код>System.IO для работы с файлами:
  6. using System.IO;
    ...
    
  7. В функции Main объявите переменную для хранения пути к файлу. Лучше использовать дословную строку (символ @):
  8. string path = @"L01.dat";
    
  9. Мы собираемся вывести числа из файла в нескольких строках: <код> N чисел в каждой строке. В функции main нам нужно запросить пользователя число <код>N:
  10. ...
    WriteLine($"Введите N:");
    var n = int.Parse(ReadLine());
    
  11. Затем необходимо объявить метод с именем PrintFileInRows с двумя параметрами — путь к файлу и количество N:
  12. // после закрывающей фигурной скобки основной функции
    static void PrintFileInRows(string path, int n = 10)
          {
           ...
          }
    
    Метод ничего не вернет, поэтому используем ключевое слово void.
  13. Чтобы прочитать что-то из файла, сначала нужно открыть файл. Но в соответствии с требованием задачи у нас должно быть два блока try..catch, один из них обрабатывает ошибки, возникающие при открытии файла, а другой блок обрабатывает ошибки, возникающие при работе с уже открытым файлом.
  14. Итак, давайте сначала создадим блоки try..catch, а затем поместим в них остальной код:
  15. // в фигурных скобках метода
    try
                {
                   /* здесь будет код, в котором могут возникать ошибки
                   при открытии файла*/
                    {
                        try
                        {
                        /* здесь будет код, в котором могут возникать ошибки
                       при работе с уже открытым файлом*/
                        }
                        catch (IOException e)
                        {
                         // сообщение об ошибке при работе с файлом
                        }
                    }
                }
                catch (IOException e)
                {
                // сообщение об ошибке при открытии файла
                }
            }
       
    
  16. Чтобы открыть файл, необходимо указать путь к файлу в вашей системе и режим открытия — как операционная система должна открывать файл. Поместите следующий код после открытой фигурной скобки первого блока try..catch:
  17. using (var f = File.Open(path, FileMode.Open))
    {
    ...
    }
    
  18. После этого мы собираемся работать с файлом, это означает, что мы будем считывать некоторые данные из файла и что-то с ними делать. Итак, нам нужно создать переменную (br) как экземпляр класса BinaryReader, чтобы иметь возможность считывать данные из файла в виде двоичных значений. Следующий код должен быть помещен во второй блок try..catch, который находится внутри инструкции using:
  19. // после открытой фигурной скобки оператора using
     try
         {
             var br = new BinaryReader(f);
    
  20. В соответствии с требованиями задачи мы должны вывести сообщение «пустой файл», если в файле нет никаких данных. Лучше использовать метод PeekChar, который возвращает -1, если файл пуст:
  21. if (br.PeekChar() == -1)
           {
               WriteLine("пустой файл");
            }
    
  22. После этого мы будем считывать числа одно за другим из файла и выводить их в консоль. Необходимо выводить <код>N чисел в каждой строке. Используем счетчик i, чтобы подсчитать, сколько чисел мы уже прочитали. Если количество считанных чисел равно N, то мы перейдем к следующей строке:
  23. // после предыдущего оператора if
    int i = 0;
    while (br.PeekChar() != -1)
          {
              if (i >= n)
                    {
                      WriteLine();
                      i = 0;
                     }
               Write(br.ReadInt32() + " ");
               i++;
           }
    
  24. В первом блоке catch мы собираемся распечатать сообщение в случае возникновения какой-либо ошибки при работе с файлом:
  25. // within the first catch block:
    catch (IOException e)
                    {
                        WriteLine($"Ошибка работы с файлом: {e.Message}");
                    }
    
    Класс IOException хранит исключение, которое генерируется при возникновении ошибки ввода-вывода.
    Message — это атрибут класса Exception, который возвращает сообщение об ошибке, объясняющее причину исключения, или пустую строку («»).
  26. Во втором блоке catch мы собираемся распечатать сообщение в случае возникновения какой-либо ошибки при открытии файла:
  27. // в пределах первого блока catch:
    catch (IOException e)
                    {
                        Console.WriteLine($"Ошибка считывания из файла: {e.Message}");
                    }
    
  28. В конце кода программы поместите метод ReadKey, чтобы не закрывать окно консоли при запуске программы в режиме отладки:
  29. // после закрывающей скобки блока фиксации:
    ReadKey();
    
    Метод ReadKey получает следующий символ или функциональную клавишу, нажатую пользователем. Нажатая клавиша отображается в окне консоли.
  30. Нажмите F5, чтобы запустить программу в режиме отладки и проверить выходные данные.
  31. Чтобы увидеть результат при возникновении ошибки, измените имя файла, например:
  32. string path = @"L02.dat";
    
  33. Нажмите F5, чтобы запустить программу в режиме отладки и проверить выходные данные.
  34. Верните правильное имя файлу.
  35. Чтобы увидеть результат при возникновении ошибки во время работы с файлом, добавьте следующую строку после инструкции while:
  36. // после закрытия фигурной скобки оператора while:
    WriteLine(br.ReadInt32());
    
    В этой строке кода мы пытаемся прочитать из файла, когда мы уже прочитали все.
  37. Нажмите F5, чтобы запустить программу в режиме отладки и проверить выходные данные.
  38. Прокомментируйте неправильную строку кода.
  39. Загрузите файл .cs в систему moodle.
Задание 2:

Выполнить: Загрузите файл Task02.dat и вставьте его в папку с проектом: (~/Lesson_14Task2/bin/Debug). В файле записаны целые числа. Создайте программу, которая сначала выведет эти числа в окно консоли, а после этого подсчитает количество чисел, которые меньше, чем их сосед слева (меньше, чем предыдущее число в последовательности чисел).

Указание 1: Создайте метод для печати данных из файла:

static void PrintData(string path)

Указание 2: Создайте метод для вывода количества чисел, меньших, чем их сосед слева:

static int CountLessThanLeft(string path)

Указание 3: Возможно, потребуется использовать определенную кодировку символов при чтении из файла:

     using (var fs = new FileStream(path, FileMode.Open))
     using (var br = new BinaryReader(fs, Encoding.ASCII))

Указание 4: Может быть несколько блоков try..catch, и каждый из них может обрабатывать разные ошибки. Напишите свой код так, чтобы один блок try..catch обрабатывал ошибки, возникающие при открытии файла, а другой блок обрабатывал ошибки, возникающие при работе с уже открытым файлом.

Тогда структура программы должна быть следующей:

...
try 
{
    using ( ... )
    using ( ... )
    { 
        try 
        {
            //работа с открытым файлом...
        }
        catch 
        {     
            // обработка ошибки при работе с уже открытым файлом
        }
   }
catch 
{
    // обработка ошибки открытия файла
}

Указание 5: Проверьте, правильно ли работают блоки try..catch. Смоделируйте ошибку при работе с уже открытым файлом и ошибку с именем файла. После проверки верните предыдущий (правильный) код и закомментируйте неправильный.

Примерный вывод:

Числа в файле:
-35 25 28 -37 13 -9 -24 12 -35 37 -24 20 29 -11 -45 -8 43 12 12 44
Количество чисел, меньших, чем их левый сосед: 8
+++++++++++++++
Числа в файле:
-35 25 28 -37 13 -9 -24 12 -35 37 -24 20 29 -11 -45 -8 43 12 12 44 Ошибка работы  с файлом: Чтение после конца потока невозможно.
Количество чисел, меньших, чем их левый сосед: 8
+++++++++++++++

[Название проекта: Lesson_14Task2, Название файла L14Task2.cs]