• Программинг

Нужны источники бесперебойного питания?

Источники бесперебойного питания от дизельстор

C#. Копирование больших файлов

Известно, что в .NET для копирования файлов программистами используется метод Copy, класса File. В большинстве случаев вариант использования File.Copy оправдывает себя. Не нужно ни о чем беспокоиться, достаточно только передать путь к исходному и целевому файлу – система сама все проконтролирует: считает информацию из исходного файла, создаст новый и запишет его. При этом разработчик никак не может повлиять на процесс копирования – прервать его, поставить на паузу. Еще одна проблема возникает при копировании файлов большого размера. Если это выполнять через метод Copy и не в отдельном потоке, то ваше приложении будет в зависшем состоянии, пока процесс копирование не завершиться. Для решения этих проблем я написал класс, который побайтно копирует файл и в процессе передает данные приложению. Весь процесс работает исключительно на родных функциях библиотеки .NET, поэтому функций WIN API вы здесь не найдете.

Также, в этой статье мы разберем как работать с объектами класса FileStream.

Давайте разберем структуру класса.

Класс FileCopy содержит в себе всего два метода. И только один из них виден «снаружи». Для наглядности, в классе, я описал только самое необходимое, чтобы было понятно как происходит процесс чтения и записи.

Сперва я описал два делегата:

1 public delegate void Complet(bool ifComplete);
2 public delegate void Progress(string message, int procent);

Они нужны будут для описание следующих событий:

1 /// <summary>
2 /// Событие на завершение копирования файла
3 /// </summary>
4 public event Complet OnComplete;
5 /// <summary>
6 /// Событие во время копирования
7 /// </summary>
8 public event Progress OnProgress;

Далее в коде будут вызываться эти события, и все, кто на них подписался, получат сообщения.

Следующая переменная устанавливает размер порции или сколько за раз будет считано и записано информации. Размер в байтах. Значение этой переменной можно изменить во время работы программы.

1 public int BufferLenght { get; set; }

Теперь перейдем к самому главному методу, в котором и будет проходить копирование данных. Вот полный код метода с описанием (не пугайтесь, на самом деле он короче, если отбросить комментарии =)):

1 /// <summary>
2 /// Копирование файла
3 /// </summary>
4 /// <param name="sourceFile">Путь к исходному файлу</param>
5 /// <param name="destinationFile">Путь к целевому файлу</param>
6 public void CopyFile(string sourceFile, string destinationFile)
7 {
8     try
9     {
10         //Создаем буфер по размеру исходного файла
11         //В буфер будем записывать информацию из файла
12         Byte[] streamBuffer = new Byte[BufferLenght];
13         //Общее количество считанных байт
14         long totalBytesRead = 0;
15         //Количество считываний
16         //Используется для задания периода отправки сообщений
17         int numReads = 0;
18  
19         //Готовим поток для исходного файла
20         using (FileStream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read))
21         {
22             //Получаем длину исходного файла
23             long sLenght = sourceStream.Length;
24             //Готовим поток для целевого файла
25             using (FileStream destinationStream = new FileStream(destinationFile, FileMode.Create, FileAccess.Write))
26             {
27                 //Читаем из буфера и записываем в целевой файл
28                 while (true) //Из цикла выйдем по окончанию копирования файла
29                 {
30                     //Увеличиваем на единицу количество считываний
31                     numReads++;
32                     //Записываем в буфер streamBuffer BufferLenght байт
33                     //bytesRead содержит количество записанных байт
34                     //это количество не может быть больше заданного BufferLenght
35                     int bytesRead = sourceStream.Read(streamBuffer, 0, BufferLenght);
36  
37                     //Если ничего не было считано
38                     if (bytesRead == 0)
39                     {
40                         //Записываем информацию о процессе
41                         getInfo(sLenght, sLenght);
42                         //и выходим из цикла
43                         break;
44                     }
45  
46                     //Записываем данные буфера streamBuffer в целевой файл
47                     destinationStream.Write(streamBuffer, 0, bytesRead);
48                     //Для статистики запоминаем сколько уже байт записали
49                     totalBytesRead += bytesRead;
50  
51                     //Если количество считываний кратно 10
52                     if (numReads % 10 == 0)
53                     {
54                         //Записываем информацию о процессе
55                         getInfo(totalBytesRead, sLenght);
56                     }
57  
58                     //Если количество считанных байт меньше буфера
59                     //Значит это конец
60                     if (bytesRead < BufferLenght)
61                     {
62                         //Записываем информацию о процессе
63                         getInfo(totalBytesRead, sLenght);
64                         break;
65                     }
66                 }
67             }
68         }
69  
70         //Отправляем сообщение что процесс копирования закончен удачно
71         if (OnComplete != null)
72             OnComplete(true);
73     }
74     catch (Exception e)
75     {
76         System.Windows.Forms.MessageBox.Show("Возникла следующая ошибка при копировании:\n" + e.Message);
77         //Отправляем сообщение что процесс копирования закончен неудачно
78         if (OnComplete != null)
79             OnComplete(false);
80     }
81 }

Как видите, я создал два объекта на основе класса FileStream. Один читает, а другой записывает из исходного файла в целевой. Таким образом, варьируя размером буфера, можно добиться довольно таки высокой скорости копирования больших файлов.

Метод getInfo просто готовит информацию и передает ее в событие.

1 private void getInfo(long totalBytesRead, long sLenght)
2 {
3     //Формируем сообщение
4     string message = string.Empty;
5     double pctDone = (double)((double)totalBytesRead / (double)sLenght);
6     message = string.Format("Считано: {0} из {1}. Всего {2}%",
7              totalBytesRead,
8              sLenght,
9              (int)(pctDone * 100));
10     //Отправляем сообщение подписавшимя на него
11     if (OnProgress != null)
12         OnProgress(message, (int)(pctDone * 100));
13 }

Сам класс не идеален, но вы можете легко доработать его под себя. Например дописать проверку на существование файла или реализовать механизм паузы во время копирования файла.

Для примера работы с классом вы можете посмотреть вот этот проект. В этом проекте описан принцип работы в отдельном потоке через компонент BackgroundWorker.