最近在写一个管理系统,有图片上传的需求。使用SpringMVC的MultipartFile很好实现,就不赘述。之前一直使用随机生成的文件名保存文件去重,但是这样无法保证文件的唯一性,之后自己想着添加一个唯一去重的功能,首先想到了MD5的。
正好我在使用的MD5工具类中有getFileMD5方法,所以第一版本直接使用工具类的方法获取文件MD5:
思路1: 先将接受到的MultipartFile对象转换成File对象,然后获取MD5值,用MD5值作为文件名保存文件,删掉临时保存的File文件;
/**
* 图片上传去重,错误代码
*
*@paramimage
*/
@RequestMapping("/upload")
publicvoidupload(@RequestParamMultipartFilemultipartFile) {
try{
if(multipartFile!=null) {
// 获取文件原名
StringoriginalFilename=multipartFile.getOriginalFilename();
// 获取文件扩展名
Stringextension= FileUtils.getExtensionByName(originalFilename);
// 随机生成唯一临时名称
StringuuidString= CommonUtil.getUUIDString(32);
StringnewFileName=uuidString+"."+extension;
// 将文件临时保存
Filedest=newFile(savePath,newFileName);
if(!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
multipartFile.transferTo(dest);
// 获取文件MD5值
StringfileMD5= MD5.getFileMD5(dest);
// 将MD5值作为最终文件名
StringfinalName=fileMD5+"."+extension;
FilefinalFile=newFile(dest.getParent() +"/"+finalName);
if(!finalFile.exists()) {
// 复制文件
FileUtils.copyFile(dest,finalFile);
}
// 删除临时文件
booleandelete=dest.delete();
if(!delete) {
System.out.println("文件删除失败:"+dest.getCanonicalPath());
}
System.out.println(dest.getName());
}
}catch(IOExceptione) {
e.printStackTrace();
}
}
运行时发现dest临时文件每次都删不掉,这样就导致,文件还是越来越多。一开始我以为是什么地方使用了流没有关闭,深入源码去分析发现没有流未关闭的情况。经过多次测试,问题定位在getFileMD5方法。这个方法内部的流也都关闭,看起来好像也没有什么问题。困扰了我很久,最后认真了解这个方法使用的FileChannel和MappedByteBuffer这两个类。
FileChannel是一个用读写,映射和操作一个文件的通道。通过将源文件和目标文件的文件通道连接,直接将数据写入目标文件中,避免分别进行读取和写入操作,提高文件操作的效率;
MappedByteBuffer使用内存映射缓冲区读取文件,提高文件读写的效率;
乍一看两个类都只是提高文件传输效率的,好像没什么问题。但是去API仔细一看,就发现MappedByteBuffer这个类有坑;MappedByteBuffer存在内存占用和文件关闭等不确定问题。被MappedByteBuffer打开的文件只有在垃圾收集时才会被关闭,而这个点是不确定的。javadoc里是这么说的:
A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected. ——JavaDoc
最终确定是MappedByteBuffer这个类搞的鬼导致文件被占用,删除不掉。
问题找到,所以我放弃使用之前工具类的MD5方法,自己写了一个从文件流获取MD5的方法。
思路2:文件存储的逻辑就变成了:从MultipartFile中获取输入流,获取流的MD5值,保存。
这样,不仅完美解决需求,相比之前的代码,精简了很多步骤。
/**
* 图片上传
*
*@paramimage
*/
@RequestMapping("/upload")
publicvoidupload(@RequestParamMultipartFilemultipartFile) {
StringsavePath="C:\\Users\\ph\\Desktop\\";
try{
if(multipartFile!=null) {
// 从MultipartFile中获取输入流
InputStreamin=multipartFile.getInputStream();
// 获取流的MD5值
StringstreamMD5= MD5.getStreamMD5(in);
// 获取文件原名
StringoriginalFilename=multipartFile.getOriginalFilename();
// 获取文件扩展名
Stringextension= FileUtils.getExtensionByName(originalFilename);
// 使用MD5值作为文件名
StringnewFileName=streamMD5+"."+extension;
// 将文件存到服务器
Filedest=newFile(savePath,newFileName);
if(!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
if(!dest.exists()) {
multipartFile.transferTo(dest);
}
System.out.println(dest.getName());
}
}catch(IOExceptione) {
e.printStackTrace();
}
}
附上MD5工具类的两个方法:
publicclassMD5 {
privatestaticfinalLoggerlogger= LoggerFactory.getLogger(MD5.class);
protectedstaticcharhexDigits[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e',
'f'};
protectedstaticMessageDigestmessagedigest=null;
static{
try{
messagedigest= MessageDigest.getInstance("MD5");
}catch(NoSuchAlgorithmExceptione) {
logger.error("MD5FileUtil messagedigest初始化失败",e);
}
}
publicstaticString getFileMD5(Filefile) {
FileInputStreamin=null;
FileChannelch=null;
Stringmd5="";
try{
in=newFileInputStream(file);
ch=in.getChannel();
// MappedByteBuffer存在内存占用和文件关闭等不确定问题。如果后续有操作文件需求,不建议使用
MappedByteBufferbyteBuffer=ch.map(FileChannel.MapMode.READ_ONLY, 0,file.length());
messagedigest.update(byteBuffer);
byteBuffer.clear();
md5=bufferToHex(messagedigest.digest());
System.out.println(md5);
}catch(FileNotFoundExceptione) {
e.printStackTrace();
}catch(IOExceptione) {
e.printStackTrace();
}finally{
if(ch!=null) {
try{
ch.close();
}catch(IOExceptione) {
e.printStackTrace();
}
}
if(in!=null) {
try{
in.close();
}catch(IOExceptione) {
e.printStackTrace();
}
}
}
returnmd5;
}
publicstaticString getStreamMD5(InputStreamin) {
Stringmd5="";
try{
ByteBufferbyteBuffer= ByteBuffer.wrap(IOUtils.toByteArray(in));
messagedigest.update(byteBuffer);
byteBuffer.clear();
md5=bufferToHex(messagedigest.digest());
}catch(FileNotFoundExceptione) {
e.printStackTrace();
}catch(IOExceptione) {
e.printStackTrace();
}finally{
if(in!=null) {
try{
in.close();
}catch(IOExceptione) {
e.printStackTrace();
}
}
}
returnmd5;
}
privatestaticString bufferToHex(bytebytes[]) {
returnbufferToHex(bytes, 0,bytes.length);
}
privatestaticString bufferToHex(bytebytes[],intm,intn) {
StringBufferstringbuffer=newStringBuffer(2 *n);
intk=m+n;
for(intl=m;l
appendHexPair(bytes[l],stringbuffer);
}
returnstringbuffer.toString();
}
privatestaticvoidappendHexPair(bytebt, StringBufferstringbuffer) {
charc0=hexDigits[(bt& 0xf0) >> 4];
charc1=hexDigits[bt& 0xf];
stringbuffer.append(c0);
stringbuffer.append(c1);
}
}