小文件复制时使用File.Copy()方法非常方便,但在程序中复制大文件系统将处于假死状态(主线程忙于复制大量数据),你也许会说使用多线程就可以解决这个问题了,但是如果文件过大,没有显示复制时的进度就会让用户处于盲目的等待中。下面的示例使用文件流分块形式复制文件解决这个问题,但发现块的大小选择很关键且速度好像还是没有直接使用Windows中自带的复制速度快:
显示源代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;
namespace SimpleDemo
{
/// <summary>
/// 大文件复制
/// </summary>
public partial class frm16BigFileCopy : Form
{
public frm16BigFileCopy()
{
InitializeComponent();
}
private void btnFrom_Click(object sender, EventArgs e)
{
//使用打开文件对话框指定要复制的源大文件
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == DialogResult.OK)
{
txtFrom.Text = ofd.FileName;
}
}
}
private void btnTo_Click(object sender, EventArgs e)
{
//使用打开文件对话框指定要复制到的目标大文件
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == DialogResult.OK)
{
txtTo.Text = ofd.FileName;
}
}
}
private void btnStart_Click(object sender, EventArgs e)
{
progressBar1.Value = 0;
lblInfo.Text = "0.0%";
//实例化一个线程,使用Lambda表达式初始化对象
Thread t = new Thread(() =>
{
//单次复制时块的大小,以B为单位
int sectionSize = 1024 * 200;
//获得要复制的源文件流
FileStream from = new FileStream(txtFrom.Text, FileMode.Open, FileAccess.Read);
//获得要复制的目标文件流,文件模式为添加
FileStream to = new FileStream(txtTo.Text, FileMode.Append, FileAccess.Write);
//如果源文件长度小于单次复制时块的大小
//小文件不用多次复制
if (from.Length > sectionSize)
{
//已复制长度
long copied = 0;
//当剩下的长度比单次复制时块要小时退出循环
while (from.Length - copied >= sectionSize)
{
//从文件流中把指定长度的字节复制到目录流中
CopySection(from, to, sectionSize);
//使源文件流的当前位置与目标文件流同步
to.Position = from.Position;
//累加已复制的长度
copied += sectionSize;
//计算已复制比率
long rate = copied * 100 / from.Length;
//将操作交给主线程
this.Invoke((MethodInvoker)delegate()
{
//显示完成进度信息
progressBar1.Value = (int)rate;
lblInfo.Text = rate.ToString() + "%";
});
}
//计算剩余未复制的字节数
int left = Convert.ToInt32(from.Length - copied);
//将剩余的最后部分复制完成
CopySection(from, to, left);
}
else
{
//从文件流中把指定长度的字节复制到目录流中
CopySection(from, to, (int)from.Length);
}
from.Dispose();
to.Dispose();
//将操作交给主线程
this.Invoke((MethodInvoker)delegate()
{
//显示完成进度信息
progressBar1.Value = 100;
lblInfo.Text = "100%";
MessageBox.Show("复制完成");
});
});
//线程开始运行
t.Start();
}
/// <summary>
/// 从文件流中把指定长度的字节复制到目录流中
/// </summary>
/// <param name="from">源文件流</param>
/// <param name="to">目标文件流</param>
/// <param name="len">要复制的长度</param>
private static void CopySection(FileStream from, FileStream to, int len)
{
//实例化一个临时字节缓冲数组
byte[] buffer = new byte[len];
//从源文件流中读取0到len长度的字节到buffer中
from.Read(buffer, 0, len);
//清除该流的缓冲区,缓冲的数据都将写入到文件系统
from.Flush();
//将0到len长度的字节从buffer中写入到目标文件流中
to.Write(buffer, 0, len);
//清除该流的缓冲区,缓冲的数据都将写入到文件系统
to.Flush();
}
}
}
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;
namespace SimpleDemo
{
/// <summary>
/// 大文件复制
/// </summary>
public partial class frm16BigFileCopy : Form
{
public frm16BigFileCopy()
{
InitializeComponent();
}
private void btnFrom_Click(object sender, EventArgs e)
{
//使用打开文件对话框指定要复制的源大文件
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == DialogResult.OK)
{
txtFrom.Text = ofd.FileName;
}
}
}
private void btnTo_Click(object sender, EventArgs e)
{
//使用打开文件对话框指定要复制到的目标大文件
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == DialogResult.OK)
{
txtTo.Text = ofd.FileName;
}
}
}
private void btnStart_Click(object sender, EventArgs e)
{
progressBar1.Value = 0;
lblInfo.Text = "0.0%";
//实例化一个线程,使用Lambda表达式初始化对象
Thread t = new Thread(() =>
{
//单次复制时块的大小,以B为单位
int sectionSize = 1024 * 200;
//获得要复制的源文件流
FileStream from = new FileStream(txtFrom.Text, FileMode.Open, FileAccess.Read);
//获得要复制的目标文件流,文件模式为添加
FileStream to = new FileStream(txtTo.Text, FileMode.Append, FileAccess.Write);
//如果源文件长度小于单次复制时块的大小
//小文件不用多次复制
if (from.Length > sectionSize)
{
//已复制长度
long copied = 0;
//当剩下的长度比单次复制时块要小时退出循环
while (from.Length - copied >= sectionSize)
{
//从文件流中把指定长度的字节复制到目录流中
CopySection(from, to, sectionSize);
//使源文件流的当前位置与目标文件流同步
to.Position = from.Position;
//累加已复制的长度
copied += sectionSize;
//计算已复制比率
long rate = copied * 100 / from.Length;
//将操作交给主线程
this.Invoke((MethodInvoker)delegate()
{
//显示完成进度信息
progressBar1.Value = (int)rate;
lblInfo.Text = rate.ToString() + "%";
});
}
//计算剩余未复制的字节数
int left = Convert.ToInt32(from.Length - copied);
//将剩余的最后部分复制完成
CopySection(from, to, left);
}
else
{
//从文件流中把指定长度的字节复制到目录流中
CopySection(from, to, (int)from.Length);
}
from.Dispose();
to.Dispose();
//将操作交给主线程
this.Invoke((MethodInvoker)delegate()
{
//显示完成进度信息
progressBar1.Value = 100;
lblInfo.Text = "100%";
MessageBox.Show("复制完成");
});
});
//线程开始运行
t.Start();
}
/// <summary>
/// 从文件流中把指定长度的字节复制到目录流中
/// </summary>
/// <param name="from">源文件流</param>
/// <param name="to">目标文件流</param>
/// <param name="len">要复制的长度</param>
private static void CopySection(FileStream from, FileStream to, int len)
{
//实例化一个临时字节缓冲数组
byte[] buffer = new byte[len];
//从源文件流中读取0到len长度的字节到buffer中
from.Read(buffer, 0, len);
//清除该流的缓冲区,缓冲的数据都将写入到文件系统
from.Flush();
//将0到len长度的字节从buffer中写入到目标文件流中
to.Write(buffer, 0, len);
//清除该流的缓冲区,缓冲的数据都将写入到文件系统
to.Flush();
}
}
}
问题:我试过单次复制时块的大小sectionSize取值与复制的速度有很大的关系,不知道有那位能告诉我怎样才能计算出每次sectionSize的取值最合理。
当然我还有另外一种想法不过没有用代码实现,就是在复制时使用多个线程同时将一个文件流中数据复制到目标位置去合并,理论上应该可以实现,且会成倍加速,有点类似BT,不知道大家还有没有别的好办法,愿意学习。