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%

Теги: ,

Если у вас возникли вопросы, вы можете оставить их в комментариях

Комментарии к статье

2 Ответов на “C#. Копирование больших файлов”

Оставить комментарий

(обязательно)

(обязательно)