• 大电子文件读取成二进制流方案


    最近开发的时候遇到用户提到的BT需求,泥马要把上G的电子文件导入到系统数据库中,这不是坑爹吗?还天天发邮件打电话来催,没办法,用户就是上帝!我们这帮苦逼的程序猿也得照样着,以下就说下这几天的研究过程吧!

    问题出现的背景:

    以前上传电子文件在读取文件的时候,遇到大电子文件的时候就会时不时给你来个OutOfMemoryException这坑爹的异常,问了下度娘原因是多种多样的!有涉及到修改服务器的配置啊什么的,服务器咱也动不了,这类方案就没尝试了。所以只有从自己的代码上做文章了!

    原来的代码如下:

    View Code
    #region 根据文件的完整路径获取二进制文件(old读取大文件的时候会报异常)
            /// <summary>
            /// 从指定的文件路径中读取文件,返回文件二进制数据
            /// </summary>
            /// <param name="FileName">文件名称</param>
            /// <param name="FilePath">文件的完整路径</param>
            public byte[] ReadFileOld(string FileName, string FilePath)
            {
                try
                {
                    //创建文件流
                    FileStream fsReader = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    MemoryStream mem = new MemoryStream();
                    byte[] buffer = new byte[1024];
                    int bytesRead = 0;
                    int TotalByteRead = 0;
                    while (true)
                    {
                        bytesRead = fsReader.Read(buffer, 0, buffer.Length);
                        TotalByteRead += bytesRead;
                        if (bytesRead == 0)
                            break;
                        mem.Write(buffer, 0, bytesRead);
                    }
                    if (mem.Length > 0)
                    {
                        byte[] bytes=mem.ToArray();
                        fsReader.Close();
                        fsReader.Dispose();
                        mem.Dispose();
                        mem.Close();
                        return bytes;
                    }
                    else
                    {
                        return null;
                    }
                }
                catch (Exception ep)
                {
                    throw ep;
                }
            }
            #endregion

    此方法读取电子文件的时候,文件过大就会出现OutOfMemoryException异常,分析发现罪魁祸首就是MemoryStream,该流是牺牲内存来读取文件的,当计算机的内存被占用到一定的数量的时候就会出现该异常!

    解决方案:摒弃内存相关的流,就出现如下的方法

    View Code
    #region 根据文件的完整路径获取二进制文件(测试用,也可上传大点的电子文件)
            /// <summary>
            /// 从指定的文件路径中读取文件,返回文件二进制数据
            /// </summary>
            /// <param name="FileName">文件名称</param>
            /// <param name="FilePath">文件的完整路径</param>
            public byte[] ReadFileTest(string FileName, string FilePath)
            {
                try
                {
                    //创建文件流
                    FileStream Reader = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    
                    // 创建一个二进制数据流读入器,和打开的文件关联
                    BinaryReader brMyfile = new BinaryReader(Reader);
                    // 把文件指针重新定位到文件的开始
                    brMyfile.BaseStream.Seek(0, SeekOrigin.Begin);
                    byte[] bytes = brMyfile.ReadBytes(Convert.ToInt32(Reader.Length.ToString()));
                    // 关闭以上new的各个对象
                    brMyfile.Close();
                    Reader.Dispose();//释放内存
                    return bytes;
                }
                catch (Exception ep)
                {
                    throw ep;
                }
            }
            #endregion

    这个方法测试了几次,大点的电子文件上传不会出现内存溢出异常了,但是具体能上传多大的电子文件我也没多测试就测试了一个600M多点的电子文件!

    最终使用的方案:

    查询网上各位大牛的方案,出现大电子文件分块读取,这是一个不错的思想,不过现在项目中需要的是一次返回byte[]数组,和分块读取显示有一定的差距!所以还是自己动手来写个方法吧,具体代码如下:

    View Code
     #region 根据文件的完整路径获取二进制文件
            /// <summary>
            /// 从指定的文件路径中读取文件,返回文件二进制数据
            /// </summary>
            /// <param name="FileName">文件名称</param>
            /// <param name="FilePath">文件的完整路径</param>
            public byte[] ReadFile(string FileName, string FilePath)
            {
                FileStream fsReader = null;
                try
                {
                    //创建文件流
                    fsReader = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    
                    int bufferSize = 1024;//每次读取的字节数
                    byte[] buffer = new byte[bufferSize];//缓冲区数组
                    long fileLength = fsReader.Length;//文件流的字节总长度
                    int readCount = (int)Math.Ceiling(((double)fileLength / (double)bufferSize)); //需要对文件读取的次数
                    int currentReadCount = 0;//当前读取次数
                    byte[] totalBytes = new byte[fileLength];//用来保存文件的字节数组
                    long bytesIndex = 0;
                    //最后一次循环需要读取的字节长度,用来初始化数组,避免读取到空值的情况
                    long lastReadLength = fileLength - (readCount - 1) * bufferSize;
    
                    while (currentReadCount < readCount)
                    {
                        //分readCount次读取这个文件流,每次从上次读取的结束位置开始读取bufferSize个字节
                        long position = currentReadCount * bufferSize;
                        fsReader.Seek(position, SeekOrigin.Begin);//设置当前流的读取起始位置
                        if (currentReadCount == (readCount - 1))//最后一次
                        {
                            byte[] lastBuffer = new byte[lastReadLength];
                            bytesIndex = fsReader.Read(lastBuffer, 0, (int)lastReadLength);//读取最后一部分剩余字节
                            lastBuffer.CopyTo(totalBytes, position);//加入到需要返回的字节数组中去
                        }
                        else
                        {
                            bytesIndex = fsReader.Read(buffer, 0, bufferSize);
                            buffer.CopyTo(totalBytes, position);//加入到需要返回的字节数组中去
                        }
                        if (bytesIndex == 0)//读取完成
                        {
                            break;
                        }
    
                        currentReadCount++;//读取完成后当前读取次数加1
    
                    }
                    fsReader.Dispose();
                    fsReader.Close();
                    return totalBytes;
                }
                catch (Exception ep)
                {
                    if (fsReader != null)
                    {
                        fsReader.Dispose();
                    }
                    throw ep;
                }
                finally
                {
                    if (fsReader != null)
                    {
                        fsReader.Dispose();
                    }
                }
            }
            #endregion

    用这个方法做了一个cs模式的导入工具给用户来导入电子文件,大的电子文件导入没太大问题了!不过没试过太大的!

    别以为这样就完了,尼玛的用户提了一个需求涉及到从FTP上读取大电子文件并保存到数据库中,没办法只好继续研究!

    FTP读取下载一般文件的类我就不提了,问下度娘就能找到FtpHelper类,我主要说下自己写的读取大文件的类吧,有了之前CS模式读取大电子文件的经验,心中暗自高兴!以前的研究终于用上了啊!于是Copy过来修改了一下读取流的方式,代码如下:

    View Code
    /// <summary>
            /// 从FTP服务器下载文件,返回文件二进制数据
            /// </summary>
            /// <param name="RemoteFileName">远程文件名</param>
            public byte[] DownloadFileOld(string RemoteFileName)
            {
                Stream fsReader = null;
                try
                {
                    if (!IsValidFileChars(RemoteFileName))
                    {
                        throw new Exception("Invalid File Name or Directory Name!");
                    }
                    Response = Open(new Uri(this.Uri.ToString() + RemoteFileName), WebRequestMethods.Ftp.DownloadFile);
                    fsReader = Response.GetResponseStream();
    
                    int bufferSize = 1024;//每次读取的字节数
                    byte[] buffer = new byte[bufferSize];//缓冲区数组
                    long fileLength = fsReader.Length;//文件流的字节总长度
                    int readCount = (int)Math.Ceiling(((double)fileLength / (double)bufferSize)); //需要对文件读取的次数
                    int currentReadCount = 0;//当前读取次数
                    byte[] totalBytes = new byte[fileLength];//用来保存文件的字节数组
                    long bytesIndex = 0;
                    //最后一次循环需要读取的字节长度,用来初始化数组,避免读取到空值的情况
                    long lastReadLength = fileLength - (readCount - 1) * bufferSize;
    
                    while (currentReadCount < readCount)
                    {
                        //分readCount次读取这个文件流,每次从上次读取的结束位置开始读取bufferSize个字节
                        long position = currentReadCount * bufferSize;
                        fsReader.Seek(position, SeekOrigin.Begin);//设置当前流的读取起始位置
                        if (currentReadCount == (readCount - 1))//最后一次
                        {
                            byte[] lastBuffer = new byte[lastReadLength];
                            bytesIndex = fsReader.Read(lastBuffer, 0, (int)lastReadLength);//读取最后一部分剩余字节
                            lastBuffer.CopyTo(totalBytes, position);//加入到需要返回的字节数组中去
                        }
                        else
                        {
                            bytesIndex = fsReader.Read(buffer, 0, bufferSize);
                            buffer.CopyTo(totalBytes, position);//加入到需要返回的字节数组中去
                        }
                        if (bytesIndex == 0)//读取完成
                        {
                            break;
                        }
    
                        currentReadCount++;//读取完成后当前读取次数加1
    
                    }
                    fsReader.Dispose();
                    fsReader.Close();
                    return totalBytes;
    
                }
                catch (Exception ep)
                {
                    if (fsReader != null)
                    {
                        fsReader.Dispose();
                    }
                    ErrorMsg = ep.ToString();
                    throw ep;
                }
                finally
                {
                    if (fsReader != null)
                    {
                        fsReader.Dispose();
                    }
                }
            }

    尼玛一测试报一个该流不支持查找的操作,读取FTP上的文件是通过NetworkStream流来反应的,和读取文件流不一样,上MSDN上一查,坑爹!NetworkStream为了安全不支持seek等查找方法,以下方法就不能使用了!

    fsReader.Seek(position, SeekOrigin.Begin);//设置当前流的读取起始位置

    和查找相关的方法啊属性都不能使用了,看来只有自己想办法来读取NetworkStream中的数据了,经过测试最终实现了方法如下:

    View Code
    /// <summary>
            /// 从FTP服务器下载文件,返回文件二进制数据
            /// </summary>
            /// <param name="RemoteFileName">远程文件名</param>
            public byte[] DownloadFile(string RemoteFileName)
            {
                Stream Reader=null;
                try
                {
                    if (!IsValidFileChars(RemoteFileName))
                    {
                        throw new Exception("Invalid File Name or Directory Name!");
                    }
                    Response = Open(new Uri(this.Uri.ToString() + RemoteFileName), WebRequestMethods.Ftp.DownloadFile);
                    Reader = Response.GetResponseStream();
    
                    int bufferSize = 1024;//每次读取的字节数
                    byte[] buffer;//缓存区数组
                    int bytesRead = 0;
                    long TotalByteRead = 0;//记录文件的总的字节数
                    List<byte[]> listBytes = new List<byte[]>();//记录每次读取的byte数组
                    while (true)
                    {
                        buffer = new byte[bufferSize];//缓冲区数组
                        bytesRead = Reader.Read(buffer, 0, buffer.Length);
                        TotalByteRead += bytesRead;
                        if ((bytesRead < bufferSize) && (bytesRead != 0))//读取到最后一次了
                        {
                            //Reader.Seek((TotalByteRead - bytesRead),SeekOrigin.Begin);//NetworkStream流不支持查找功能
                            MemoryStream mem = new MemoryStream();
                            mem.Write(buffer, 0, bytesRead);
                            buffer = new byte[bytesRead];
                            buffer = mem.ToArray();
                            mem.Dispose();
                        }
    
                        if (bytesRead == 0)
                        {
                            break;
                        }
                        else
                        {
                            listBytes.Add(buffer);
                            buffer = null;
                        }
    
                    }
                    //定义一个字节数组来保存需要返回的二进制数据
                    byte[] totalBytes = new byte[TotalByteRead];
                    long startIndex = 0;//元素复制的索引起始值
                    for (int i = 0; i < listBytes.Count; i++)
                    {
                        if (i == 0)
                        {
                            startIndex = 0;
                        }
                        else
                        {
                            startIndex += listBytes[i - 1].Length;
                        }
    
                        listBytes[i].CopyTo(totalBytes, startIndex);
                    }
    
                    Reader.Dispose();
                    Reader.Close();
                    return totalBytes;
                }
                catch (Exception ep)
                {
                    ErrorMsg = ep.ToString();
                    throw ep;
                }
            }

    程式发布到服务器上去也能从FTP上读取大电子文件了,用户那边也有个交代了!

  • 相关阅读:
    建议使用nullptr而不是NULL
    二叉树的遍历(三种遍历方式,前中后序;递归,栈,迭代实现)
    455.分发饼干Easy Leetcode
    java 报错:Implicit super constructor Point() is undefined. Must explicitly invoke another constructor
    求解字符串所包含子串的个数
    数组去重
    vue watch
    mysql设置指定字段不能为负数
    Shell脚本监控服务存活状态及异常,并触发钉钉webhook报警
    Shell常用语法
  • 原文地址:https://www.cnblogs.com/StevenDu/p/doNET.html
Copyright © 2020-2023  润新知