大文件分段复制实践

需求概要

  由于拷贝的文件比较大,有几G、十几G、几十G单个的文件,并且需要整个目录拷贝,直接用复制粘贴的操作导致内存开销不够,需要建立任务性的拷贝任务,并且把单个文件分段读写。中间有人工中断操作或者被意外中断的可能,下次续传需要接上一次的拷贝进度继续拷贝。

分析功能概要
1.可建立源目录到目标目录的复制任务

2.任务明细包括目录的文件以及子目录递归的所有子文件,并且在目标目录建立相同的子目录结构存放文件。

3.支持操作中断传送任务,以及支持意外中断后续传

4.任务进行中有可视化的任务进度反馈

实现方案

1.基于硬盘之间的拷贝,使用winform桌面应用程序实现功能需求。

界面效果:

2.既然需要记录任务,需要使用数据库记录信息,使用免安装的文件型数据库Sqlite。

3.建立任务表,用于记录拷贝任务;

文件复制任务表:

HT_CopyJob

字段名称

类型

备注说明

HT_ID

Integer

标识

FromDirectoryPath

Varchar

来源目录

ToDirectoryPath

Varchar

目标目录

CreateTime

Datetime

创建时间

FileCount

Integer

总文件数

CopyCount

Integer

当前已复制文件数

LeaveCount

Integer

剩下复制文件数

4.建立任务明细表,使用FileStream缓冲读取和写入,先将流放入内存,再写入文件,每次读取流的位置,要根据上一次最后读取的位置继续读取;记录文件拷贝的信息,以及上一次拷贝的进度,拷贝的情况。

文件复制明细表:

HT_CopyFile

字段名称

类型

备注说明

HT_ID

Integer

标识

CopyJob_ID

Integet

复制任务ID,ht_copyjob.HT_ID

FromPath

Varchar

来源路径

ToPath

Varchar

目标路径

LenCount

Integer

文件总大小

CopyLenCount

Integer

已经复制大小

Position

Integer

记录当前流的位置

IsStart

Integer

是否开始复制0:否,1:开始

IsFinish

Integer

是否完成,0否,1:完成

Msg

Varchar

复制消息

 5.创建复制任务主要代码

 if (txt_CopyPath.Text.Trim() == "" || txt_targetPath.Text.Trim() == "")
            {
                MessageBox.Show("请完整选择复制目录和目标目录");
                return;
            }
            List<HT.Model.HT_CopyJobModel> list_cjModel = cjBLL.GetModelList(string.Format(" and FromDirectoryPath='{0}' and ToDirectoryPath='{1}' ", txt_CopyPath.Text.Trim(), txt_targetPath.Text.Trim()));
            if (null != list_cjModel && list_cjModel.Count > 0)
            {
                DialogResult result = MessageBox.Show("该相同目录已经存在" + list_cjModel.Count + "个任务,是否还要继续创建?", "警告信息", MessageBoxButtons.YesNoCancel);
                if (result == DialogResult.No || result == DialogResult.Cancel)
                {
                    return;
                }
            }
            HT.Model.HT_CopyJobModel cjModel = new HT.Model.HT_CopyJobModel();
            cjModel.CreateTime = DateTime.Now;
            cjModel.FromDirectoryPath = txt_CopyPath.Text.Trim();
            cjModel.ToDirectoryPath = txt_targetPath.Text.Trim();
            int cjid = cjBLL.Add(cjModel);
            if (cjid > 0)
            {
                int filecount = 0;
                GetAllDirList(cjid, txt_CopyPath.Text.Trim(), txt_CopyPath.Text.Trim(), txt_targetPath.Text.Trim(), ref filecount);
                cjModel = cjBLL.GetModel(cjid);
                cjModel.FileCount = filecount;
                cjModel.LeaveCount = filecount;
                cjModel.CopyCount = 0;
                cjBLL.Update(cjModel);
                // FreeConsole();
                MessageBox.Show("任务创建成功,该任务有" + filecount + "个文件需要复制,请到任务列表开始任务");
            }
            else
            {
                MessageBox.Show("创建任务错误,请重新操作");
            }
View Code

递归读取子目录的文件,并且保存到数据库

/// <summary>
        /// 获取子目录以及文件路径保存
        /// </summary>
        /// <param name="cjid">任务ID</param>
        /// <param name="startDir">源路径</param>
        /// <param name="strBaseDir">上一级路径</param>
        /// <param name="targetPath">目标路径</param>
        private void GetAllDirList(int cjid, string startDir, string strBaseDir, string targetPath, ref int fileCount)
        {
            //AllocConsole();
            DirectoryInfo dit = new DirectoryInfo(strBaseDir);
            SaveFilePath(cjid, startDir, dit, targetPath, ref fileCount);
            DirectoryInfo[] list_dit = dit.GetDirectories();
            for (int i = 0; i < list_dit.Length; i++)
            {
                GetAllDirList(cjid, startDir, list_dit[i].FullName, targetPath, ref fileCount);
            }
        }
        /// <summary>
        /// 找到文件夹里的文件,并保存文件路径
        /// </summary>
        /// <param name="cjid">任务ID</param>
        /// <param name="startDir">源路径</param>
        /// <param name="dit">文件夹</param>
        /// <param name="targetPath">目标路径</param>
        private void SaveFilePath(int cjid, string startDir, DirectoryInfo dit, string targetPath, ref int fileCount)
        {
            HT.BLL.HT_CopyFileBLL cfBLL = new HT.BLL.HT_CopyFileBLL();
            HT.Model.HT_CopyFileModel cfModel = null;
            FileInfo[] files = dit.GetFiles();
            if (null != files && files.Count() > 0)
            {
                foreach (var fl in files)
                {
                    try
                    {
                        cfModel = new HT.Model.HT_CopyFileModel();
                        cfModel.CopyJob_ID = cjid;
                        cfModel.FromPath = fl.FullName;
                        cfModel.IsFinish = 0;
                        cfModel.IsStart = 0;
                        cfModel.LenCount = fl.Length + "";
                        cfModel.Msg = "";
                        cfModel.Position = "0";
                        cfModel.ToPath = fl.FullName.Replace(startDir, targetPath);
                        cfModel.CopyLenCount = "0";
                        int cfid = cfBLL.Add(cfModel);
                        if (cfid > 0)
                        {
                            fileCount++;
                            //Console.WriteLine(fl.FullName + " 添加记录成功");
                            richTextBox_Log.Text += fl.FullName + " 添加记录成功
";
                        }
                        else
                        {
                            //写入错误日志
                        }
                    }
                    catch { }
                }
            }
        }
View Code

6.开始执行复制任务主要代码

文件分段读写主要代码:

  //文件复制方法
        private bool CopyFileGo(HT.Model.HT_CopyFileModel cfModel, string fromPath, string toPath, int eachReadLength)
        {
            //将源文件 读取成文件流
            FileStream fromFile = new FileStream(fromPath, FileMode.Open, FileAccess.Read);
            FileInfo fi = new FileInfo(toPath);
            var di = fi.Directory;
            if (!di.Exists)
            {
                di.Create();
            }
            //已追加的方式 写入文件流
            FileStream toFile = new FileStream(toPath, FileMode.Append, FileAccess.Write);
            if (toFile.Length == fromFile.Length)
            {
                cfModel.IsFinish = 1;//已拷贝完成
                cfModel.CopyLenCount = fromFile.Length + "";
                cfBLL.Update(cfModel);
                fromFile.Close();
                toFile.Close();
                return false;
            }
            if (toFile.Length > fromFile.Length)
            {
                cfModel.IsFinish = 2;//拷贝出错了
                cfModel.CopyLenCount = toFile.Length + "";
                cfModel.Msg = "目标文件比源文件大了";
                cfBLL.Update(cfModel);
                fromFile.Close();
                toFile.Close();
                return false;
            }
            //实际读取的文件长度
            int toCopyLength = 0;
            //如果每次读取的长度小于 源文件的长度 分段读取
            if (eachReadLength < fromFile.Length)
            {
                byte[] buffer = new byte[eachReadLength];
                long copied = toFile.Length;
                while (copied <= fromFile.Length - eachReadLength && updateWorker.CancellationPending == false)
                {
                    fromFile.Position = toFile.Length; ;//读之前指定读取的位置
                    toCopyLength = fromFile.Read(buffer, 0, eachReadLength);
                    fromFile.Flush();
                    //toFile.Position = fromFile.Position-eachReadLength;//写之前指定写的位置,等于对完文件后的位置减去读的大小
                    toFile.Write(buffer, 0, eachReadLength);
                    toFile.Flush();
                    copied += toCopyLength;
                    cfModel.CopyLenCount = copied + "";
                    cfModel.Position = fromFile.Position + "";
                    cfBLL.Update(cfModel);
                    progressBarFile.Value += progressBarFile.Step;
                    label_File.Text = "文件(" + cfModel.FromPath + ")进度:" + progressBarFile.Value + "/" + progressBarFile.Maximum;
                    //Console.WriteLine(fromPath + "在分割复制,总共大小为:" + fromFile.Length + "目前进度:" + copied);
                }
                if (updateWorker.CancellationPending)//已取消后台操作
                {
                    fromFile.Close();
                    toFile.Close();
                    return false;
                }
                int left = (int)(fromFile.Length - copied);
                fromFile.Position = toFile.Length;
                toCopyLength = fromFile.Read(buffer, 0, left);
                fromFile.Flush();
                //toFile.Position = fromFile.Position-eachReadLength;
                toFile.Write(buffer, 0, left);
                toFile.Flush();
                cfModel.CopyLenCount = (copied + left) + "";
                cfModel.Position = fromFile.Position + "";
                cfModel.IsFinish = 1;
                cfBLL.Update(cfModel);
                progressBarFile.Value = progressBarFile.Maximum;
                label_File.Text = "文件(" + cfModel.FromPath + ")进度:" + progressBarFile.Value + "/" + progressBarFile.Maximum;
                //Console.WriteLine(fromPath + "总共大小为:" + fromFile.Length + "目前进度:" + copied);
            }
            else
            {
                if (updateWorker.CancellationPending)//已取消后台操作
                {
                    fromFile.Close();
                    toFile.Close();
                    return false;
                }
                //如果每次拷贝的文件长度大于源文件的长度 则将实际文件长度直接拷贝
                byte[] buffer = new byte[fromFile.Length];
                fromFile.Read(buffer, 0, buffer.Length);
                fromFile.Flush();
                toFile.Write(buffer, 0, buffer.Length);
                toFile.Flush();
                cfModel.CopyLenCount = fromFile.Length + "";
                cfModel.Position = fromFile.Position + "";
                cfModel.IsFinish = 1;
                cfBLL.Update(cfModel);
                progressBarFile.Value = progressBarFile.Maximum;
                label_File.Text = "文件(" + cfModel.FromPath + ")进度:" + progressBarFile.Value + "/" + progressBarFile.Maximum;
            }
            fromFile.Close();
            toFile.Close();
            return true;
        }
View Code

开始执行复制任务主要代码:

void GoCopyJob(object sender, DoWorkEventArgs e)
        {
            if (cjid == 0)
            {
                MessageBox.Show("请选择执行任务编号");
                return;
            }
            HT.Model.HT_CopyJobModel cjModel = cjBLL.GetModel(cjid);
            if (null != cjModel)
            {
                progressBarJob.Maximum = cjModel.FileCount;
                progressBarJob.Value = cjModel.CopyCount;
                progressBarJob.Step = 1;
                label_Job.Text = "任务(" + cjid + ")进度:" + cjModel.CopyCount + @"/" + cjModel.FileCount;
                List<HT.Model.HT_CopyFileModel> list_cfModel = cfBLL.GetModelList(string.Format(" and CopyJob_ID={0} and (IsStart=0 or IsFinish=0) order by IsStart DESC", cjid));
                if (null != list_cfModel && list_cfModel.Count > 0)
                {
                    foreach (var item in list_cfModel)
                    {
                        if (updateWorker.CancellationPending)
                        { return; }//已取消后台操作
                        progressBarFile.Maximum = int.Parse((long.Parse(item.LenCount) / 1024 / 1024) + "");//转化单位为M
                        progressBarFile.Value = int.Parse((long.Parse(item.CopyLenCount) / 1024 / 1024) + "");
                        progressBarFile.Step = eachReadLength / 1024 / 1024;
                        label_File.Text = "文件(" + item.FromPath + ")进度:" + progressBarFile.Value + "/" + progressBarFile.Maximum;
                        item.IsStart = 1;
                        if (CopyFileGo(item, item.FromPath, item.ToPath, eachReadLength))
                        {
                            //单个文件复制完成,更新界面显示进度
                            cjModel.CopyCount++;
                            cjModel.LeaveCount--;
                            cjBLL.Update(cjModel);
                            if (progressBarJob.Value < progressBarJob.Maximum)
                            {
                                progressBarJob.Value += progressBarJob.Step;
                                label_Job.Text = "任务(" + cjid + ")进度:" + cjModel.CopyCount + @"/" + cjModel.FileCount;
                            }
                        }
                        else
                        {
                            //有文件复制不成功了
                        }
                    }
                    if (progressBarJob.Maximum == progressBarJob.Value)
                    {
                        MessageBox.Show("任务执行完毕");
                    }
                    DataBind();
                }
                else
                {
                    cjModel.CopyCount = cjModel.FileCount;
                    cjModel.LeaveCount = 0;
                    if (cjBLL.Update(cjModel))
                    {
                        MessageBox.Show("已经拷贝完了");
                        DataBind();
                    }
                }
            }
            else
            {
                MessageBox.Show("该任务不存在");
            }
        }
View Code

以上就是主要的实现设计与主要的代码了,还有很多未完善和待完善的地方。很少写文章,语言组织上很难讲的明白,还需要以后多写了。欢迎大家指教。

需要源码的欢迎留下联系哦。

原文地址:https://www.cnblogs.com/zknu/p/5740551.html