zoukankan      html  css  js  c++  java
  • 大文件分段复制实践

    需求概要

      由于拷贝的文件比较大,有几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

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

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

  • 相关阅读:
    ac通过Parallels Desktop虚拟机实现共享windows独有软件提供的特殊网络11
    ac通过Parallels Desktop虚拟机实现共享windows独有软件提供的特殊网络9
    新东方智慧教室:全方位的智慧教室解决方案
    告别开发
    Unity中Awake的执行时间点
    警惕C#事件使用过程中的GC陷阱
    概率生成函数(高清重置版)暨 [CTSC2006]歌唱王国
    Leaflet中使用leafletecharts插件实现Echarts的Migration迁徙图
    Leaflet中使用leafletecharts插件实现Echarts的Migration迁徙图(带炫光特效)
    Nginx映射本地json文件,配置解决浏览器跨域问题,提供前端get请求模拟数据
  • 原文地址:https://www.cnblogs.com/zknu/p/5740551.html
Copyright © 2011-2022 走看看