在做断点续传功能的时候碰到了如题的问题困扰了我好久,就在不久之前我把这个问题解决了,特地写此文章分享给大家,也希望会对大家有所帮助!
首先对当时的背景做一下简单的描述,首先文件(压缩包,里面存放的是图片)由客户端(APP)上传至服务器,压缩包上传完成之后,服务器会对客户端所上传的压缩包文件进行MD5校验,校验通过则解压压缩包并对里面的每一张图片再做MD5校验。不过很奇怪的是在对压缩包做MD5校验的时候没有出现任何问题,而开始对图片进行MD5校验的时候就出现了如题所示的异常。他们调用的都是同一个方法啊,怎么会出错呢?百思不得其解。
下面我们来看代码:
#region MD5文件校验
///
/// MD5文件校验
///
///
///
public string GetFileMD5(string path)
{
string ret = "";
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] md5byte = md5.ComputeHash(fs);
int i, j;
foreach (byte b in md5byte)
{
i = Convert.ToInt32(b);
j = i >> 4;
ret += Convert.ToString(j, 16);
j = ((i << 4) & 0x00ff) >> 4;
ret += Convert.ToString(j, 16);
}
fs.Flush();
fs.Close();
fs.Dispose();
return ret;
}
}
#endregion
从代码中我们可以看到从文件的打开到关闭都是在using语句块中进行的,文件打开之后也进行了及时的关闭,在对压缩包进行MD5校验的时候都没出错,所以这段代码出错的可能性还是比较小的。
那出错的可能性只有是在压缩包解压那里里了,下面我们来看看解压文件的原始代码:
///
/// 文件解压。
/// 压缩文件绝对路径:FileToUpZip
/// 文件解压的文件夹路径:ZipedFolder
/// 解压时用到的密码:password,没有密码可传空
///
/// 压缩文件绝对路径
/// 文件解压的文件夹路径
/// 解压时用到的密码
public static void ZipToFile(string FileToUpZip, string ZipedFolder, string password)
{
if (File.Exists(FileToUpZip))
{
if (!Directory.Exists(ZipedFolder))
{
Directory.CreateDirectory(ZipedFolder);
}
ZipInputStream s = null;
ZipEntry theEntry = null;
string fileName;
FileStream streamWriter = null;
try
{
using (s = new ZipInputStream(File.OpenRead(FileToUpZip)))
{
s.Password = password;
while ((theEntry = s.GetNextEntry()) != null)
{
if (theEntry.Name != String.Empty)
{
fileName = Path.Combine(ZipedFolder, theEntry.Name);
///判断文件路径是否是文件夹
if (fileName.EndsWith("/") || fileName.EndsWith("\\"))
{
Directory.CreateDirectory(fileName);
continue;
}
using (streamWriter = File.Create(fileName);)
{
int size = 2048;
byte[] data = new byte[2048];
while (true)
{
size = s.Read(data, 0, data.Length);
if (size > 0 && size <= 2048)
{
streamWriter.Write(data, 0, size);
}
else
{
break;
}
}
}
}
}
}
}
catch (Exception ex)
{
AddTxtLog("文件解压出错,详细错误信息:" + ex.Message + " " + ex.StackTrace + "!");
}
finally
{
if (streamWriter != null)
{
AddTxtLog("文件解压完成,进来释放资源!");
streamWriter.Flush();
streamWriter.Close();
streamWriter.Dispose();
streamWriter = null;
}
if (theEntry != null)
{
theEntry = null;
}
if (s != null)
{
s.Close();
s.Dispose();
s = null;
}
GC.Collect();
GC.Collect(1);
}
}
}
后来我就针对这里打开文件的方法( File.Create())专门查了MSDN,终于被我发现了问题的端倪所在。下面请看修改后的代码:
///
/// 文件解压。
/// 压缩文件绝对路径:FileToUpZip
/// 文件解压的文件夹路径:ZipedFolder
/// 解压时用到的密码:password,没有密码可传空
///
/// 压缩文件绝对路径
/// 文件解压的文件夹路径
/// 解压时用到的密码
public static void ZipToFile(string FileToUpZip, string ZipedFolder, string password)
{
if (File.Exists(FileToUpZip))
{
if (!Directory.Exists(ZipedFolder))
{
Directory.CreateDirectory(ZipedFolder);
}
ZipInputStream s = null;
ZipEntry theEntry = null;
string fileName;
FileStream streamWriter = null;
try
{
//此方法与 FileMode 值为 Open、FileAccess 值为 Read 和 FileShare 值为 Read 的 FileStream(String, FileMode, FileAccess, FileShare) 构造函数重载等效。
using (s = new ZipInputStream(File.OpenRead(FileToUpZip))) //此处已经采用using语句的写法在执行完之后会自动释放资源,
//即使采用File.OpenRead()方法也不影响压缩包的使用
{
s.Password = password;
while ((theEntry = s.GetNextEntry()) != null)
{
if (theEntry.Name != String.Empty)
{
fileName = Path.Combine(ZipedFolder, theEntry.Name);
///判断文件路径是否是文件夹
if (fileName.EndsWith("/") || fileName.EndsWith("\\"))
{
Directory.CreateDirectory(fileName);
continue;
}
//这种写法会引起文件被占用的问题
//streamWriter = File.Create(fileName); //由此方法创建的 FileStream 对象的 FileShare 值默认为 None;直到关闭原始文件句柄后,
//其他进程或代码才能访问这个创建的文件。
using (streamWriter = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
{
int size = 2048;
byte[] data = new byte[2048];
while (true)
{
size = s.Read(data, 0, data.Length);
if (size > 0 && size <= 2048)
{
streamWriter.Write(data, 0, size);
}
else
{
break;
}
}
streamWriter.Flush(); //在文件流关闭前清空缓存
}
}
}
}
}
catch (Exception ex)
{
AddTxtLog("文件解压出错,详细错误信息:" + ex.Message + " " + ex.StackTrace + "!");
}
finally
{
//if (streamWriter != null)
//{
// AddTxtLog("文件解压完成,进来释放资源!");
// streamWriter.Flush();
// streamWriter.Close();
// streamWriter.Dispose();
// streamWriter = null;
//}
//if (theEntry != null)
//{
// theEntry = null;
//}
//if (s != null)
//{
// s.Close();
// s.Dispose();
// s = null;
//}
GC.Collect();
GC.Collect(1);
}
}
}
大家请注意看注释,由此方法创建的 FileStream 对象的 FileShare 值默认为 None;直到关闭原始文件句柄后,其他进程或代码才能访问这个创建的文件。对于“关闭原始文件句柄后”这段话我不是特别理解,还请大神不吝赐教,我认为应该是直到这段代码被下一次调用的时候其他进程或代码才能访问这个创建的文件。至于为什么会这样我不清楚,但File.Create()这个方法在文件创建后该方法体内会用到的话最好还是不要用这种方式。这段文件解压的代码我在网上看到过,几乎是完全从网上复制过来的,唉,差点被这段代码搞死啊。