大文件复制时,如何确定最优的块取值大小以提升效率?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1429个文字,预计阅读时间需要6分钟。
使用 `File.Copy()` 方法复制小文件非常方便,但在处理大文件时,程序可能会陷入假死状态(主线程忙于复制大量数据)。为了解决这个问题,可以考虑使用多线程。多线程可以帮助分散复制任务,减轻主线程的负担。但是,如果文件过大,这种方法可能仍不适用。
小文件复制时使用File.Copy()方法非常方便,但在程序中复制大文件系统将处于假死状态(主线程忙于复制大量数据),你也许会说使用多线程就可以解决这个问题了,但是如果文件过大,没有显示复制时的进度就会让用户处于盲目的等待中。下面的示例使用文件流分块形式复制文件解决这个问题,但发现块的大小选择很关键且速度好像还是没有直接使用Windows中自带的复制速度快:
显示源代码
usingSystem;
usingSystem.Collections.Generic;
usingSystem.ComponentModel;
usingSystem.Data;
usingSystem.Drawing;
usingSystem.Text;
usingSystem.Windows.Forms;
usingSystem.IO;
usingSystem.Threading;
namespaceSimpleDemo
{
///<summary>
///大文件复制
///</summary>
publicpartialclassfrm16BigFileCopy:Form
{
publicfrm16BigFileCopy()
{
InitializeComponent();
}
privatevoidbtnFrom_Click(objectsender,EventArgse)
{
//使用打开文件对话框指定要复制的源大文件
using(OpenFileDialogofd=newOpenFileDialog())
{
if(ofd.ShowDialog()==DialogResult.OK)
{
txtFrom.Text=ofd.FileName;
}
}
}
privatevoidbtnTo_Click(objectsender,EventArgse)
{
//使用打开文件对话框指定要复制到的目标大文件
using(OpenFileDialogofd=newOpenFileDialog())
{
if(ofd.ShowDialog()==DialogResult.OK)
{
txtTo.Text=ofd.FileName;
}
}
}
privatevoidbtnStart_Click(objectsender,EventArgse)
{
progressBar1.Value=0;
lblInfo.Text="0.0%";
//实例化一个线程,使用Lambda表达式初始化对象
Threadt=newThread(()=>
{
//单次复制时块的大小,以B为单位
intsectionSize=1024*200;
//获得要复制的源文件流
FileStreamfrom=newFileStream(txtFrom.Text,FileMode.Open,FileAccess.Read);
//获得要复制的目标文件流,文件模式为添加
FileStreamto=newFileStream(txtTo.Text,FileMode.Append,FileAccess.Write);
//如果源文件长度小于单次复制时块的大小
//小文件不用多次复制
if(from.Length>sectionSize)
{
//已复制长度
longcopied=0;
//当剩下的长度比单次复制时块要小时退出循环
while(from.Length-copied>=sectionSize)
{
//从文件流中把指定长度的字节复制到目录流中
CopySection(from,to,sectionSize);
//使源文件流的当前位置与目标文件流同步
to.Position=from.Position;
//累加已复制的长度
copied+=sectionSize;
//计算已复制比率
longrate=copied*100/from.Length;
//将操作交给主线程
this.Invoke((MethodInvoker)delegate()
{
//显示完成进度信息
progressBar1.Value=(int)rate;
lblInfo.Text=rate.ToString()+"%";
});
}
//计算剩余未复制的字节数
intleft=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>
///<paramname="from">源文件流</param>
///<paramname="to">目标文件流</param>
///<paramname="len">要复制的长度</param>
privatestaticvoidCopySection(FileStreamfrom,FileStreamto,intlen)
{
//实例化一个临时字节缓冲数组
byte[]buffer=newbyte[len];
//从源文件流中读取0到len长度的字节到buffer中
from.Read(buffer,0,len);
//清除该流的缓冲区,缓冲的数据都将写入到文件系统
from.Flush();
//将0到len长度的字节从buffer中写入到目标文件流中
to.Write(buffer,0,len);
//清除该流的缓冲区,缓冲的数据都将写入到文件系统
to.Flush();
}
}
}
问题:我试过单次复制时块的大小sectionSize取值与复制的速度有很大的关系,不知道有那位能告诉我怎样才能计算出每次sectionSize的取值最合理。
当然我还有另外一种想法不过没有用代码实现,就是在复制时使用多个线程同时将一个文件流中数据复制到目标位置去合并,理论上应该可以实现,且会成倍加速,有点类似BT,不知道大家还有没有别的好办法,愿意学习。
本文共计1429个文字,预计阅读时间需要6分钟。
使用 `File.Copy()` 方法复制小文件非常方便,但在处理大文件时,程序可能会陷入假死状态(主线程忙于复制大量数据)。为了解决这个问题,可以考虑使用多线程。多线程可以帮助分散复制任务,减轻主线程的负担。但是,如果文件过大,这种方法可能仍不适用。
小文件复制时使用File.Copy()方法非常方便,但在程序中复制大文件系统将处于假死状态(主线程忙于复制大量数据),你也许会说使用多线程就可以解决这个问题了,但是如果文件过大,没有显示复制时的进度就会让用户处于盲目的等待中。下面的示例使用文件流分块形式复制文件解决这个问题,但发现块的大小选择很关键且速度好像还是没有直接使用Windows中自带的复制速度快:
显示源代码
usingSystem;
usingSystem.Collections.Generic;
usingSystem.ComponentModel;
usingSystem.Data;
usingSystem.Drawing;
usingSystem.Text;
usingSystem.Windows.Forms;
usingSystem.IO;
usingSystem.Threading;
namespaceSimpleDemo
{
///<summary>
///大文件复制
///</summary>
publicpartialclassfrm16BigFileCopy:Form
{
publicfrm16BigFileCopy()
{
InitializeComponent();
}
privatevoidbtnFrom_Click(objectsender,EventArgse)
{
//使用打开文件对话框指定要复制的源大文件
using(OpenFileDialogofd=newOpenFileDialog())
{
if(ofd.ShowDialog()==DialogResult.OK)
{
txtFrom.Text=ofd.FileName;
}
}
}
privatevoidbtnTo_Click(objectsender,EventArgse)
{
//使用打开文件对话框指定要复制到的目标大文件
using(OpenFileDialogofd=newOpenFileDialog())
{
if(ofd.ShowDialog()==DialogResult.OK)
{
txtTo.Text=ofd.FileName;
}
}
}
privatevoidbtnStart_Click(objectsender,EventArgse)
{
progressBar1.Value=0;
lblInfo.Text="0.0%";
//实例化一个线程,使用Lambda表达式初始化对象
Threadt=newThread(()=>
{
//单次复制时块的大小,以B为单位
intsectionSize=1024*200;
//获得要复制的源文件流
FileStreamfrom=newFileStream(txtFrom.Text,FileMode.Open,FileAccess.Read);
//获得要复制的目标文件流,文件模式为添加
FileStreamto=newFileStream(txtTo.Text,FileMode.Append,FileAccess.Write);
//如果源文件长度小于单次复制时块的大小
//小文件不用多次复制
if(from.Length>sectionSize)
{
//已复制长度
longcopied=0;
//当剩下的长度比单次复制时块要小时退出循环
while(from.Length-copied>=sectionSize)
{
//从文件流中把指定长度的字节复制到目录流中
CopySection(from,to,sectionSize);
//使源文件流的当前位置与目标文件流同步
to.Position=from.Position;
//累加已复制的长度
copied+=sectionSize;
//计算已复制比率
longrate=copied*100/from.Length;
//将操作交给主线程
this.Invoke((MethodInvoker)delegate()
{
//显示完成进度信息
progressBar1.Value=(int)rate;
lblInfo.Text=rate.ToString()+"%";
});
}
//计算剩余未复制的字节数
intleft=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>
///<paramname="from">源文件流</param>
///<paramname="to">目标文件流</param>
///<paramname="len">要复制的长度</param>
privatestaticvoidCopySection(FileStreamfrom,FileStreamto,intlen)
{
//实例化一个临时字节缓冲数组
byte[]buffer=newbyte[len];
//从源文件流中读取0到len长度的字节到buffer中
from.Read(buffer,0,len);
//清除该流的缓冲区,缓冲的数据都将写入到文件系统
from.Flush();
//将0到len长度的字节从buffer中写入到目标文件流中
to.Write(buffer,0,len);
//清除该流的缓冲区,缓冲的数据都将写入到文件系统
to.Flush();
}
}
}
问题:我试过单次复制时块的大小sectionSize取值与复制的速度有很大的关系,不知道有那位能告诉我怎样才能计算出每次sectionSize的取值最合理。
当然我还有另外一种想法不过没有用代码实现,就是在复制时使用多个线程同时将一个文件流中数据复制到目标位置去合并,理论上应该可以实现,且会成倍加速,有点类似BT,不知道大家还有没有别的好办法,愿意学习。

