//Путь к несуществующему файлу. File.Delete (path2);
//Копировать файл.
File.Copy (path, path2);
Console.WriteLine ("{0} был скопирован в {1}.", path, path2);
// Удалить только что созданный файл. File.Delete (path2);
Console.WriteLine ("{0} был успешно удален.", path2);
}
catch (Exception e)
{
Console.WriteLine ("ОШИБКА: {0}", e);
}
}
}
4. Что осталось за рамками лекций по вводу-выводу.
Класс NetworkStream – получает и посылает байты через сеть процессору, работающему на другом конце сетевого соединения.
Класс BufferedStream – выполняет буферизацию данных для повышения эффективности ввода-вывода.
Класс System.Security.Cryptography.CryptoStream – потоковая реали-
зация криптографии.
Класс DriveInfo – получить список доступных устройств и информацио о любом из них.
Класс System.Security.AccessControl.FileSecurity – хранит список управления доступом (ACL – access control lists) к файлу (папке) на который ссылается.
Класс System.Security.AccessControl.FileSystemAccessRule – отдельное право доступа.
Класс Microsoft.Win32.Registry – предоставляет ключи для класса
RegistryKey.
Класс Microsoft.Win32.RegistryKey – работает с ключами реестра (создание, чтение, модификация).
Методические рекомендации по использованию средств вво- да-вывода
Открытие файла
1. Если требуется указать режим открытия и доступа:
fs = new FileStream ("testdata", FileMode.Create); //Read и Write
fs = new FileStream ("testdata", FileMode.Open,
FileAccess.Read);
//
только
Read
fs = new FileStream ("testdata", FileMode.Append,
211
FileAccess.Write); // только Write
Связать поток с обработчиком потока:
StreamWriter sw = new StreamWriter (fs);
// для записи
StreamReader sw = new StreamReader (fs);
// для чтения
2. Более простой вариант открытия на запись. Если файл не существует, он создается:
StreamWriter sw = new StreamWriter ("C:\\Temp\\Text.txt");
StreamWriter sw = new StreamWriter ("Text.txt", true); // Append
3. Более простой вариант открытия на чтение. Файл должен существовать:
StreamReader sw = new StreamReader ("C:\\Temp\\Text.txt");
4. В случае использования методов WriteAllLines или ReadAllLines открытие файла осуществляется автоматически перед выполнением операции записи/чтения. После чтения/записи файл закрывается.
Текстовые файлы для просмотра редактором
Файлы с последовательным доступом
•Для просмотра файла текстовым редактором.
•Без последующей разборки записей на данные.
•Самое дружественное представление информации на экране.
Примечание. Записи не структурированы, поэтому не предполагается их разбор на переменные. Каждая запись – это одна переменная типа string.
Форматирование:
Метод Format() для каждой записи, то есть одна запись – это одна строка. В записи, кроме переменных, предполагается наличие связующих слов.
str = Format("Это {0}-ая строка", i);
Вывод в файл:
Способ 1. В цикле методом WriteLine класса StreamWriter:
sw. WriteLine ("Группа 171-1");
sw. WriteLine (Format("Студент {0}, курс {1}", fio, kurs) );
Способ 2. Накапливать строки в массиве строк и выводить их одним вызовом статического метода WriteAllLines класса File:
string[ ] text = { "Hello", "And", "Welcome" }; File.WriteAllLines (@"c:\temp\Text.txt", text );
212
Проблема: не всегда при создании массива строк изначально известен его размер.
Чтение:
только последовательное методом ReadLine в цикле или методом ReadAllLines за один вызов без последующей разборки записи на данные.
Достоинство метода WriteAllLines: размерность массива строк устанавливается методом.
Найти требуемую строку можно методами класса String, например, StartsWith.
Загрузка в базу данных:
не предполагается, так как все строки разные.
•Для просмотра файла текстовым редактором.
•Для редактирования файла вручную из текстового редактора.
•Для загрузки файла в программу и разбиения записей на составляющие данные.
1. Выводимые данные могут содержать внутри себя произвольное количество пробелов (разделителей).
Примечание. Объединить все данные в одну строку можно, но из-за наличия неконтролирумых пробелов разобрать такую запись после ее чтения из файла на данные не представляется возможным (если использовать пробел как разделитель).
Поэтому предлагается способ вывода каждой переменной в виде отдельной строки.
Форматирование:
Каждая нестроковая переменная преобразуется в строку методом ToString. В каждой записи в строго фиксированном месте могут присутствовать связующие слова.
int kurs ;
str = ToString(kurs) ;
Вывод в файл:
Способ 1. Каждая переменная выводится в виде отдельной строки ме-
тодом WriteLine() класса StreamWriter:
sw. WriteLine (fio);
sw. WriteLine (kurs.ToString());
Способ 2. Все выводимые строки накапливаются в массиве строк и этот массив выводится одним вызовом статического метода WriteAllLines класса File:
213
string[ ] text = new string[200] ;
text[0] = fio;
text[1] = kurs.ToString(); // и т.д. для всех перемен-
ных
. . .
// и т.д. для всех записей
text[i] = fio;
text[i+1] = kurs.ToString(); // и т.д. для всех перемен-
ных
File.WriteAllLines ("Text.txt", Text );
Проблема: не всегда при создании массива строк известен его размер. Следует использовать класс ArrayList.
Чтение:
только последовательное методом ReadLine в цикле или методом
ReadAllLines за один вызов.
Загрузка в базу данных:
Каждая прочитанная строка (т.е. переменная) преобразуется в ис-
ходный тип:
kurs = int.Parse ( text );
// после ReadLine
-или-
kurs = int.Parse ( text[i] );
// после ReadAllLines
Связующие слова пропускаются
2. Выводимые данные не содержат внутри себя пробелов (разделителей) или содержат их фиксированное количество.
Примечание. Можно объединить все данные в одну строку, отделяя их друг от друга пробелом (разделителями). Разобрать такую запись после ее чтения из файла на данные можно методом Split класса String.
Форматирование:
Формируется одна строка на запись, состоящая из данных, преобразованных в подстроки методами Format() и/или ToString.
Данные друг от друга должны отделяться одним или несколькими пробелами (уникальными разделителями), на которые при разборке будет реагировать метод Split.
В записях, кроме переменных, могут находиться связующие слова, которые при разборке строки пропускаются.
Способ 2. Накапливать строки в массиве строк и выводить их одним вызовом статического метода WriteAllLines класса File:
string[ ] text ;
. . .
File.WriteAllLines (@"c:\temp\Text.txt", text );
Чтение:
только последовательное методом ReadLine в цикле или методом
ReadAllLines за один вызов.
Загрузка в базу данных:
Каждая прочитанная строка разбивается на массив подстрок, каждая из которых потом преобразуется с помощью метода Parse:
char [ ]
delimiter = { ', ' };
// запятая и пробел - разделители
string
record = "Иванов, курс 2";
string [ ] data = null;
data = record.Split (delimiter);
// data[0] – fio, data[2] – kurs
fio = data[0] ; kurs = int.Parse (data[2]);
Связующие слова пропускаются (в примере - data[1]).
Текстовый файл с прямым доступом
•Для просмотра файла текстовым редактором.
•Для редактирования файла вручную из текстового редактора.
•Для загрузки файла в программу с использованием прямого доступа к записям.
Создавать текстовый файл с прямым доступом к записям не целесообразно, так как обеспечить одинаковую длину всех записей достаточно не просто. Например, целые числа 12 и 12345 занимают в памяти по 4 байта (для int), в то время как их символьное представление – 2 и 5 байт. Кроме того, такие записи практически нельзя редактировать текстовым редактором, так как при редактировании записей наверняка нарушится их исходная длина. Есть и еще одна проблема: русские и английские символы в файле по умолчанию кодируются разным количеством байт. Следовательно, если в одной записи будут английские буквы, а в другой – русские, то у этих записей будут разные размеры.
Вывод: в подавляющем большинстве случаев текстовые файлы предназначены только для последовательного чтения записей.
Двоичные файлы для загрузки в программу Дв. файлы с последовательным доступом к записям
Для сохранения данных в файле и последующей загрузки файла в программу. Просмотр текстовым редактором бессмысленен.
215
1. Запись представляет собой совокупность отдельных переменных, которые могут составлять информационную часть объекта.
Примечание.
А. Для вывода и ввода записей рекомендуется использовать двоичный поток, создаваемый классами BinaryWriter и BinaryReader.
Б. Понятие записи в этом случае – это логическое объединение данных, например, одного объекта. В отличие от строк, двоичные данные друг от друга ничем не отделяются.
Форматирование:
не требуется, так как данные выводятся в их внутреннем (двоичном) формате.
Вывод данных в файл:
FileStream fs;
int
i
= 10;
double
d
= 1023.56;
string
str = "Привет студентам!";
fs = new FileStream ("testdata", FileMode.Create); BinaryWriter dataOut = new BinaryWriter (fs); dataOut.Write (i);
dataOut.Write (d); dataOut.Write (str);
Чтение и загрузка в базу данных:
только последовательное методами Read...() в цикле:
fs = new FileStream ("testdata", FileMode.Open); BinaryReader dataIn = new BinaryReader(fs);
i = dataIn.ReadInt32(); d = dataIn.ReadDouble(); str = dataIn.ReadString();
2. Сохранение и восстановление объектов.
Примечание. Для вывода и ввода объектов рекомендуется использовать сериализацию (сохранение) и десериализацию (восстановление) объектов.
using System; using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable] class Student
{
216
private int
kurs ;
// номер курса
private string fio;
// Фамилия студента
public Student (int k, string f)
{ kurs = k;
fio = f;
}
}
class SaveObj
{
public static void Main()
{
FileStream fs;
Student st1 = new Student (1, "Тенишева");
Student st2 = new Student (2, "Иванов");
// Создание потока
fs = new FileStream ("Student.dat", FileMode.Create);
//Воспользуемся двоичным форматированием
BinaryFormatter bf = new BinaryFormatter();
//Сохраним объект в файле в двоичном виде
bf.Serialize (fs, st1);
bf.Serialize (fs, st2);
fs.Close();
// Восстановим из файла сериализованный объект fs = new FileStream ("Student.dat", FileMode.Open);
Для сохранения данных в файле и выборочного доступа к записям (на ввод и вывод).
Примечание. Для реализации прямого доступа к записям необходимо иметь способ определения адреса записи в файле. Адресом является смещение до первого байта требуемой записи от начала файла. Первый байт файла имеет смещение 0.
Существует два основных способа решения этой задачи.
1. Все записи файла имеют одинаковую (известную) длину.
В этом случае нет смысла использовать какую-либо дополнительную информацию о месторасположении записей.
1) Для обеспечения одинаковых размеров всех записей необходи-
мо:
•чтобы значения строковой переменной во всех записях имели одинаковую длину. Для этого необходимо каждое значение дополнить пробелами до требуемого размера (см. метод
PadRight (int);).
217
•во-вторых, чтобы все символы – русские и английские – кодировались одинаковым количеством байтов (например, по 2 байта), необходимо в классе кодировки выбрать Unicode.
2)При определении адреса требуемой записи, необходимо учесть, что размер каждой строки в записи файла вдвое больше числа ее символов (Unicode – 2 байта), плюс один байт на каждую строку записи. В этом байте хранится длина строки.
Для ввода-вывода рекомендуется использовать двоичный поток. Поиск требуемых записей может быть организован как на диске, так и в оперативной памяти.
1.1 Поиск требуемых записей, выполняется на диске
Примечание. Это случай, когда файл невозможно целиком прочитать в память или когда количество операций ввода-вывода невелико.
int
kurs = 1;
//4 байта
string
fio = "Иванов
"; //15 символов -> 31 б.
fs = new FileStream ("testdata", FileMode.Create); dataOut = new BinaryWriter (fs, Encoding.Unicode); dataOut.Write (kurs);
fs = new FileStream ("testdata", FileMode.Open); dataIn = new BinaryReader(fs, Encoding.Unicode);
fs.Seek(35, SeekOrigin.Begin); //указатель на 2-ю запись
// Длина записи = 4+(15*2)+1 = 35
kurs = dataIn.ReadInt32();
// читаем вторую запись
fio = dataIn.ReadString();
1.2 Все операции с файлом выполняются в оперативной памяти
Примечание. Если необходимо осуществлять выборочное чтение большого количества записей из небольшого двоичного файла (Size < 2 Гб), то предпочтительнее будет прочитать весь файл последовательно в оперативную память, в массивы переменных, входящих в состав запи-
218
сей. Совокупность всех элементов массивов с одинаковым индексом относятся к одному объекту.
Постоянная длина записей позволяет определить их количество:
int n = (int)fs.Length / 35;
// количество записей
Прямой файл всегда может быть прочитан последовательно:
int[ ]
kursAr
= new int[n] ;
string[ ]
fioAr
= new string[n];
. . .
for (int i = 0;
i < n; i++)
{
kursAr[i] = dataIn.ReadInt32();
//
(i+1) - я
fioAr[i]
= dataIn.ReadString();
//
запись
}
2. Все записи двоичного файла имеют разную длину.
Такая ситуация, как правило, возникает из-за наличия строковых полей, имеющих разную длину.
Вместо того чтобы обеспечивать постоянство длины записей, при их выводе в файл запоминаются адреса записей в дополнительном (индексном) файле.
Если все адреса записывать в файл индексов в двоичном формате как переменную одного из целых типов (byte, ushort, uint или ulong), то такой файл будет файлом с прямым доступом. Техника работы с файлом прямого доступа была описана выше.
После определения адреса требуемой записи выполняется позиционирование в файле данных и выполнение операций считывания двоичных данных записи.
Данный подход можно использовать для прямого доступа к сериализованным объектам.
Недостатком такого подхода является то, что при модификации строковых полей записи, как правило, изменяется их длина, поэтому модифицировать можно только нестроковые поля (или обеспечивать постоянство длины строк в сериализованных объектах).
Читать двоичный файл в байтовый массив методом ReadAllBytes() или другим байтовым методом, а затем организовывать выборку данных из этого массива, кажется простой задачей. Но отображение байтов на типы данных – это непростые алгоритмы.
Таким образом, для прямого доступа к записям файл должен быть, как правило, двоичным.
Если прямой доступ не требуется, файл может быть любым – и текстовым, и двоичным.
219
Если файл должен просматриваться в текстовом редакторе файл должен быть текстовым.