大电子文件读取成二进制流方案-创新互联

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

问题出现的背景:

目前创新互联已为成百上千家的企业提供了网站建设、域名、虚拟主机、网站改版维护、企业网站设计、景泰网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。

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

原来的代码如下:

View Code
#region 根据文件的完整路径获取二进制文件(old读取大文件的时候会报异常)
   ///    /// 从指定的文件路径中读取文件,返回文件二进制数据
///    /// 文件名称   /// 文件的完整路径   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 根据文件的完整路径获取二进制文件(测试用,也可上传大点的电子文件)
   ///    /// 从指定的文件路径中读取文件,返回文件二进制数据
///    /// 文件名称   /// 文件的完整路径   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 根据文件的完整路径获取二进制文件
   ///    /// 从指定的文件路径中读取文件,返回文件二进制数据
///    /// 文件名称   /// 文件的完整路径   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
///    /// 从FTP服务器下载文件,返回文件二进制数据
///    /// 远程文件名   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
///    /// 从FTP服务器下载文件,返回文件二进制数据
///    /// 远程文件名   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 listBytes = new List();//记录每次读取的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上读取大电子文件了,用户那边也有个交代了!


当前题目:大电子文件读取成二进制流方案-创新互联
分享链接:http://myzitong.com/article/ddopcd.html