Программирование / Заочники / Инфорамтика и программирование / Методичка (1 сем) / Урок 10 - Switch..case
.pdf
Урок 2: Принципы обработки больших файлов.
Основная цель урока.
1.Изучить основные принципы чтения\записи больших файлов (500МБ-8Гб) .
2.Изучить основные принципы XOR шифрования.
3.Продолжить изучение функций\процедур.
.
Краткая справка. |
|
|
При обработке |
файлов больших размеров пользоваться |
функциями |
« мгновенного» чтения |
в строковую переменную (или байтовый |
массив) уже |
нецелесообразно. Для чтения (записи) больших файлов используется принцип блочного чтения\записи, когда файл читается\записывается кусками по N байт.
Класс FileStream (с англ. « файловый поток») позволяет читать\записывать информацию в файлы представленную в виде байтового массива. Использование класса начинается с создания нового экземпляра класса. Ниже приведены некоторые варианты открытия файлов.
//Открытие файла C:\1.txt (файл должен быть создан) FileStream f1 = new FileStream(@"C:\1.txt", FileMode.Open); //Открытие файла 1.txt в текущей директории
//если файл не существует - будет создан новый и открыт пустой файл FileStream f2 = new FileStream("1.txt", FileMode.OpenOrCreate); //Открывает файл D:\VirtualDub\settings.ini
//если файл существует он будет перезаписан, иначе создаётся новый файл FileStream f3 = new FileStream(@"D:\VirtualDub\settings.ini", FileMode.Create);
После того как созданы экземпляры класса FileStream (в данном случае это переменные f1,f2 и f3). Можно использовать методы и свойства класса для чтения\записи в файл. Ниже приведены основные свойства\методы класса
FileStream.
Метод |
Описание |
|
|
Read |
Выполняет чтение последовательности байт из потока |
|
|
Write |
Выполняет запись последовательности байт в поток |
|
|
ReadByte |
Выполняет чтение одного байта |
|
|
WriteByte |
Выполняет запись одного байта |
|
|
Seek |
Устанавливает текущую позицию чтения\записи |
|
|
SetLength |
Устанавливает размер файла |
|
|
Close |
Закрывает файловый поток |
|
|
Свойства |
Описание |
|
|
|
|
|
|
Length |
Возвращает размер файла в байтах |
|
|
|
|
|
|
Position |
Возвращает\устанавливает |
текущую |
позицию |
|
чтения\записи |
|
|
|
|
||
Name |
Возвращает имя файла связанное с файловым потоком |
||
|
|
|
|
Блочное чтение с помощью класса FileStream выглядит следующим образом.
FileStream f = new FileStream(@"C:\1.txt", FileMode.Open); byte[] buffer = new byte[4096];
int bytesReaded = 0;
while ((bytesReaded = f1.Read(buffer, 0, 4096)) > 0)
{
//здесь можно обрабатывать содержимое buffer
}
f.Close();
Перечисления (enumeration).
Перечисление — это множество именованных целочисленных констант.
Ключевое слово enum объявляет перечислимый тип. В тех случаях, когда значения принимаемые переменной ограничены небольшим множеством значений – удобно использовать перечисления.
Формат записи перечисления таков enum имя {список_перечисления);
Пример:
enum Days { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
Использование:
Days[] holiday = {Days.Sat, Days.Sun};
Шифрование XOR.
Данный вид шифрования основан на следующем свойстве исключающего ИЛИ: A ^ B = C;
C ^ B = A;
Т.е. если представить исходное сообщение в виде массива байт, и каждый байт этого сообщения объединить с помощью исключающего ИЛИ с некоторым числом
(размером в байт, т.е. от 0 до 255) – мы получим зашифрованное исходное сообщение. Если повторно объединить уже зашифрованное сообщение с этим же числом – получим исходное сообщение.
Если объединить зашифрованное сообщение с другим числом – исходное сообщение мы уже не получим. Ниже представлен код, который осуществляет шифрование строки.
string text = "Hello, world!"; Console.WriteLine(text);
//Переводим строку в байтовый массив
byte[] data = Encoding.Unicode.GetBytes(text); //Шифруем с паролем 78
for (int i = 0; i < data.Length; i++)
{
data[i] = (byte)(data[i] ^ 78);
}
//Переводим байтовый массив в строку
string encodedText = Encoding.Unicode.GetString(data); //Зашифрованное сообщение Console.WriteLine(encodedText);
Т.к. объединяемый символ относится к типу byte, то количество возможных числовых паролей при таком подходе равно 256, зашифрованное таким образом сообщение легко расшифровать просто перебрав все варианты. Можно усложнить процесс шифрования объединяя каждый символ исходного сообщения не с одним числом, а с последовательностью чисел. Т.к. строка в некотором роде так же является последовательностью чисел – появляется возможность использовать обычные строковые пароли для шифрования сообщений. В этом случае каждый символ исходного сообщения (его числовое представление) объединяется с каждым символом пароля. Т.к. пароль обычно короче исходного сообщения, для шифрования его можно повторять столько раз сколько необходимо. Ниже представлен пример для исходной строки "Hello, world!"и пароля "abc32"
Исходная строка |
H |
e |
l |
l |
o |
, |
|
w |
o |
r |
l |
d |
! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Пароль |
a |
b |
c |
3 |
2 |
a |
b |
c |
3 |
2 |
a |
b |
c |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Зашифрованная строка |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Исходная строка ^ Пароль = Зашифрованная строка Зашифрованная строка ^ Пароль = Исходная строка
Если известно исходное и зашифрованное сообщение – можно узнать пароль, т.к.
Зашифрованная строка ^ Исходная строка = Пароль
Учебное задание 2.2.
Разработать программу позволяющую шифровать\дешифровать любые файлы
(в том числе более 4гигабайт) с указанием пароля.
Технология выполнения учебного задания 2.2.
Шаг 1. Создайте проект нового консольного приложения, назовите его Lab22.
Шаг 2. Запросим имя файла, который нужно зашифровать. Имя файла для шифрования будет храниться в переменной filenameIn, имя нового
(зашифрованного) файла будет храниться в переменной filenameOut.
static void Main(string[] args)
{
Console.WriteLine("Введите имя файла для шифрования"); string filenameIn = Console.ReadLine(); Console.WriteLine("Введите пароль");
string password = Console.ReadLine(); string filenameOut = "[crypt]" + filenameIn;
}
Листинг 1
Шаг 3. Для реализации поблочного чтения воспользуемся классом FileStream из пространства имён System.IO. Обозначим использование пространства имён
System.IO (см. листинг 2).
using System;
using System.Collections.Generic; using System.Text;
using System.IO; namespace Lab22
{
class Program
{
Листинг 2
Шаг 4. Создадим новый экземпляр класса FileStream (с англ. « файловый поток»). В коде это будет выглядеть следующим образом:
FileStream f1 = new FileStream(filenameIn, FileMode.Open);
Из вышеприведённого кода видно, что конструктор класса принимает 2
аргумента.
1 аргумент – абсолютный или относительный путь до файла.
2 аргумент – режим открытия файла. Режим открытия определяет, каким образом будет открыт этот файл – открыт существующий, создан новый или открыт для записи в конец файла. Тип этого аргумента – перечисление FileMode. Режим
FileMode.Open означает, что мы хотим открыть существующий файл.
Поблочное чтение из файла осуществляется путём вызова метода Read (класса
FileStream) в цикле. Метод Read принимает 3 аргумента.
1 аргумент – массив, куда будет записываться информация. Под массив должно быть выделено достаточно места.
2 аргумент – смещение для 1 аргумента, начиная с этого элемента, будут записываться данные в массив.
3 аргумент – количество байт, которые надо считать из файла и занести в массив.
Метод Read возвращает количество фактически прочитанных байт, т.е. если мы читаем по 8192 байт, то не факт, что размер файла будет кратен 8192 и последний вызов метода Read вернёт значение меньше чем 8192 . Например, если размер файла равен 28174 байт, то при поблочном чтении метод Read будет вызван 4 раза.
Первые 3 раза он вернёт 8192, а последний 4-ый вернёт 3598, т.к. 8192 + 8192 +
8192 + 3598 = 28174.
string password = Console.ReadLine(); string filenameOut = "[crypt]" + filenameIn; //Создаём новый экземпляр класса FileStream
FileStream f1 = new FileStream(filenameIn, FileMode.Open); //Создаём байтовый массив из 8192 элементов (8 килобайт) byte[] buffer = new byte[8192];
//в переменной bytesReaded будет храниться количество //фактически прочитанных байт
int bytesReaded = 0;
//цикл: читаем порцию данных из файла и проверяем, //что количество прочитанных байт больше 0
while ((bytesReaded = f1.Read(buffer, 0, 8192)) > 0)
{
//здесь можно обрабатывать содержимое buffer
}
//закрываем файл (обязательно!) f1.Close();
Console.ReadKey();
Листинг 3
Шаг 5. Чтение исходно файла уже реализовано. Добавим вывод в выходной файл (пока что без шифрования).
//Создаём новый экземпляр класса FileStream (f1 - исходный файл) FileStream f1 = new FileStream(filenameIn, FileMode.Open);
//f2 - зашифрованный файл
FileStream f2 = new FileStream(filenameOut, FileMode.Create); //Создаём байтовый массив из 8192 элементов (8 килобайт) byte[] buffer = new byte[8192];
//в переменной bytesReaded будет храниться количество
//фактически прочитанных байт int bytesReaded = 0;
//цикл: читаем порцию данных из файла и проверяем, //что количество прочитанных байт больше 0
while ((bytesReaded = f1.Read(buffer, 0, 8192)) > 0)
{
//Записываем прочитанный блок байт из первого файла во второй f2.Write(buffer, 0, bytesReaded);
}
//закрываем файл (обязательно!) f1.Close();
f2.Close();
Console.ReadKey();
Листинг 4
Шаг 6. Запустите программу, нажав клавишу F5. На данный момент программа не шифрует файл, а только создаёт копию файла с другим именем. Если программа написана без ошибок, исходный файл и сгенерированный файл должны быть идентичны.
Шаг 7. Добавим возможность шифрования файла. Шифрование выделим в отдельную функцию (чтобы можно было в дальнейшем перенести в другой проект).
Самостоятельно создайте новую функцию, которая принимает 3 аргумента:
1 аргумент – байтовый массив (имя переменной – buf).
2 аргумент – количество байт, которые надо зашифровать (имя переменной –
len)
3 аргумент – пароль для шифрования (имя переменной – pass).
Функция не будет возвращать никаких значений.
Имя функции задайте по вашему усмотрению (например Encrypt – с англ шифровать).
Таким образом, при вызове функция должна шифровать\дешифровать байтовый массив buf размером len байт с помощью пароля pass.
Код созданной вами функции приведён в листинге 5.
int n = 0;
for (int i = 0; i < len; i++)
{
//объеденяем с помощью поразрядного исключающего ИЛИ //каждый байт массива и каждый символ пароля
buf[i] = (byte)(buf[i] ^ (byte)pass[n]); n++;
if (n > pass.Length - 1) n = 0;
}
Листинг 5
Шаг 8. Теперь осталось добавить вызов этой функции в цикле чтения файла
(листинг 6).
while ((bytesReaded = f1.Read(buffer, 0, 8192)) > 0)
{
//Шифруем данные
ИмяВашейФункции(buffer, bytesReaded, password);
//Записываем прочитанный блок байт из первого файла во второй f2.Write(buffer, 0, bytesReaded);
}
Листинг 6
Шаг 9. Запустите программу, нажав клавишу F5. Проверьте работоспособность программы на файле размером 100-300Мб. Процесс шифрования\дешифрования при XOR-шифровании одинаковый, т.е. чтобы расшифровать файл нужно применить к нему повторно процедуру шифрования с правильным паролем.
Шаг 10. Процесс шифрования больших файлов может отнимать значительное время, добавим возможность отображения прогресса шифрования так, чтобы было видно на сколько процентов завершился процесс. Для этого будем накапливать в некоторой переменной количество считанных и обработанных байт. Процент завершения будет равен отношению этой переменной к общему размеру исходного файла.
byte[] buffer = new byte[8192];
//в переменной bytesReaded будет храниться количество //фактически прочитанных байт
int bytesReaded = 0;
//totalReaded - общее количество прочитанных байт long totalReaded = 0;
//totalBytes - размер файла filenameIn long totalBytes = f1.Length; Console.Write("Завершено:");
//цикл: читаем порцию данных из файла и проверяем, //что количество прочитанных байт больше 0
while ((bytesReaded = f1.Read(buffer, 0, 8192)) > 0)
{
//Шифруем данные
ИмяВашейФункции(buffer, bytesReaded, password);
//Записываем прочитанный блок байт из первого файла во второй f2.Write(buffer, 0, bytesReaded);
//накапливаем кол-во прочитанных байт totalReaded += bytesReaded; //вычисляем процент
long persent = totalReaded * 100 / totalBytes; Console.CursorLeft = 10;
Console.Write("{0}%", persent);
}
Листинг 7
Обратите внимание, что переменные totalReaded и totalBytes имеют тип long –
если бы использовался тип int, мы могли бы работать только с файлами до 2 ГБ
(2^32 / 2 = 2ГБ).
Самостоятельное задание 2.2.
Реализуйте шифрование файла таблицы рекордов из самостоятельного задания 2.1 так, чтобы пользователь не смог просмотреть и изменить исходные данные scores.txt, но программа могла читать шифрованный файл.
