问题出现的背景:
目前创新互联已为成百上千家的企业提供了网站建设、域名、虚拟主机、网站改版维护、企业网站设计、景泰网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。以前上传电子文件在读取文件的时候,遇到大电子文件的时候就会时不时给你来个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上读取大电子文件了,用户那边也有个交代了!