C#. Копирование больших файлов
Известно, что в .NET для копирования файлов программистами используется метод Copy, класса File. В большинстве случаев вариант использования File.Copy оправдывает себя. Не нужно ни о чем беспокоиться, достаточно только передать путь к исходному и целевому файлу — система сама все проконтролирует: считает информацию из исходного файла, создаст новый и запишет его. При этом разработчик никак не может повлиять на процесс копирования — прервать его, поставить на паузу. Еще одна проблема возникает при копировании файлов большого размера. Если это выполнять через метод Copy и не в отдельном потоке, то ваше приложении будет в зависшем состоянии, пока процесс копирование не завершиться. Для решения этих проблем я написал класс, который побайтно копирует файл и в процессе передает данные приложению. Весь процесс работает исключительно на родных функциях библиотеки .NET, поэтому функций WIN API вы здесь не найдете.
Также, в этой статье мы разберем как работать с объектами класса FileStream.
Давайте разберем структуру класса.
Класс FileCopy содержит в себе всего два метода. И только один из них виден «снаружи». Для наглядности, в классе, я описал только самое необходимое, чтобы было понятно как происходит процесс чтения и записи.
Сперва я описал два делегата:
public delegate void Complet(bool ifComplete); public delegate void Progress(string message, int procent);
Они нужны будут для описание следующих событий:
/// <summary> /// Событие на завершение копирования файла /// </summary> public event Complet OnComplete; /// <summary> /// Событие во время копирования /// </summary> public event Progress OnProgress;
Далее в коде будут вызываться эти события, и все, кто на них подписался, получат сообщения.
Следующая переменная устанавливает размер порции или сколько за раз будет считано и записано информации. Размер в байтах. Значение этой переменной можно изменить во время работы программы.
public int BufferLenght { get; set; }
Теперь перейдем к самому главному методу, в котором и будет проходить копирование данных. Вот полный код метода с описанием (не пугайтесь, на самом деле он короче, если отбросить комментарии =)):
/// <summary>
/// Копирование файла
/// </summary>
/// <param name="sourceFile">Путь к исходному файлу</param>
/// <param name="destinationFile">Путь к целевому файлу</param>
public void CopyFile(string sourceFile, string destinationFile)
{
try
{
//Создаем буфер по размеру исходного файла
//В буфер будем записывать информацию из файла
Byte[] streamBuffer = new Byte[BufferLenght];
//Общее количество считанных байт
long totalBytesRead = 0;
//Количество считываний
//Используется для задания периода отправки сообщений
int numReads = 0;
//Готовим поток для исходного файла
using (FileStream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read))
{
//Получаем длину исходного файла
long sLenght = sourceStream.Length;
//Готовим поток для целевого файла
using (FileStream destinationStream = new FileStream(destinationFile, FileMode.Create, FileAccess.Write))
{
//Читаем из буфера и записываем в целевой файл
while (true) //Из цикла выйдем по окончанию копирования файла
{
//Увеличиваем на единицу количество считываний
numReads++;
//Записываем в буфер streamBuffer BufferLenght байт
//bytesRead содержит количество записанных байт
//это количество не может быть больше заданного BufferLenght
int bytesRead = sourceStream.Read(streamBuffer, 0, BufferLenght);
//Если ничего не было считано
if (bytesRead == 0)
{
//Записываем информацию о процессе
getInfo(sLenght, sLenght);
//и выходим из цикла
break;
}
//Записываем данные буфера streamBuffer в целевой файл
destinationStream.Write(streamBuffer, 0, bytesRead);
//Для статистики запоминаем сколько уже байт записали
totalBytesRead += bytesRead;
//Если количество считываний кратно 10
if (numReads % 10 == 0)
{
//Записываем информацию о процессе
getInfo(totalBytesRead, sLenght);
}
//Если количество считанных байт меньше буфера
//Значит это конец
if (bytesRead < BufferLenght)
{
//Записываем информацию о процессе
getInfo(totalBytesRead, sLenght);
break;
}
}
}
}
//Отправляем сообщение что процесс копирования закончен удачно
if (OnComplete != null)
OnComplete(true);
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show("Возникла следующая ошибка при копировании:\n" + e.Message);
//Отправляем сообщение что процесс копирования закончен неудачно
if (OnComplete != null)
OnComplete(false);
}
}
Как видите, я создал два объекта на основе класса FileStream. Один читает, а другой записывает из исходного файла в целевой. Таким образом, варьируя размером буфера, можно добиться довольно таки высокой скорости копирования больших файлов.
Метод getInfo просто готовит информацию и передает ее в событие.
private void getInfo(long totalBytesRead, long sLenght)
{
//Формируем сообщение
string message = string.Empty;
double pctDone = (double)((double)totalBytesRead / (double)sLenght);
message = string.Format("Считано: {0} из {1}. Всего {2}%",
totalBytesRead,
sLenght,
(int)(pctDone * 100));
//Отправляем сообщение подписавшимя на него
if (OnProgress != null && !double.IsNaN(pctDone)) //Изменение 1.03.2012
OnProgress(message, (int)(pctDone * 100));
}
Сам класс не идеален, но вы можете легко доработать его под себя. Например дописать проверку на существование файла или реализовать механизм паузы во время копирования файла.
Для примера работы с классом вы можете посмотреть вот этот проект. В этом проекте описан принцип работы в отдельном потоке через компонент BackgroundWorker.

На этом у меня все. Задавайте свои вопросы.
Update 1.03.2012:
ошибка при копировании файлов нулевой длины
Популярность: 15%
Если у вас возникли вопросы, вы можете оставить их в комментариях


Интересная статья, просто о простом. Но вот нашел парочку ошибочек, подправить надо бы:
«Таким образом, варьируя размер буфера, можно добиться довольно таки высокой скорости копирования больше файлов. »
1) варьируя размерОМ буфера
довольно-таки
2) копирования большИХ файлов
3) ну и полностью придираясь
Все правильно. Поддерживаю, что писать нужно грамотно. Просто я набираю очень быстро, и уже на уровне рефлексов делаю ляпы, которые не сразу заметны.