干货!java文件上传判重姿势浅谈

简介: 文件上传,用户极有可能上传重复文件,内容完全一致。如果对上传的文件未做任何处理,对于文件存储系统来说将是灾难,大量重复的数据,如果允许上传大文件,那么对于存储资源将是巨大的浪费。

一、场景

文件上传,用户极有可能上传重复文件,内容完全一致。如果对上传的文件未做任何处理,对于文件存储系统来说将是灾难,大量重复的数据,如果允许上传大文件,那么对于存储资源将是巨大的浪费。对于重复的文件,只需要复制相应的访问地址即可,源文件可无需上传,既减轻了网络带宽压力,也减少了存储容量的压力。


二、应对:


1、通过文件名判重。非特殊情况下,不会采用这种方案,理由跟人同名一样,文件名很容易重复,随着用户上升,概率会变大。采用此方案极易导致不能达到判重的目的。


2、读取文件头加部分内容。这种方案可以解决百分之五十的问题,缺点是随着量的上升,重复的概率依然存在。


3、读取文件内容,进行hash计算,通常情况下,这种方案比较可靠,出现误判的概率低。一些分布式文件系统,如fastdfs等也是采取hash的方式进行文件判重。


三、方案


开发语言:java  JDK 1.8 IDE:eclipse


机器配置:i5双核  内存4G 64位


四、代码实现

1、org.apache.commons.codec.digest.DigestUtils

@Testpublicvoidtest1()
Stringpath="your file path";
try {
longbegin=System.currentTimeMillis();
Stringmd5=DigestUtils.md5Hex(newFileInputStream(path));
longend=System.currentTimeMillis();
System.out.println(md5);
System.out.println("time:"+ ((end-begin) /1000) +"s");
    } catch (FileNotFoundExceptione) {
e.printStackTrace();
    } catch (IOExceptione) {
e.printStackTrace();
    }
  }

2、自定义缓冲区实现

@Testpublicvoidtest2() {
Stringpath="file path";
longbegin=System.currentTimeMillis();
BigIntegerbi=null;
try {
byte[] buffer=newbyte[8192*10];
intlen=0;
MessageDigestmd=MessageDigest.getInstance("MD5");
Filef=newFile(path);
FileInputStreamfis=newFileInputStream(f);
while ((len=fis.read(buffer)) !=-1) {
md.update(buffer, 0, len);
      }
fis.close();
byte[] b=md.digest();
bi=newBigInteger(1, b);
    } catch (NoSuchAlgorithmExceptione) {
e.printStackTrace();
    } catch (IOExceptione) {
e.printStackTrace();
    }
Stringmd5=bi.toString(16);
longend=System.currentTimeMillis();
System.out.println(md5);
System.out.println("time:"+ ((end-begin) /1000) +"s");
  }

3、com.twmacinta.util.MD5定义

@Testpublicvoidtest3() {
Stringpath="file path";
longbegin=System.currentTimeMillis();
try {
Stringmd5=MD5.asHex(MD5.getHash(newFile(path)));
longend=System.currentTimeMillis();
System.out.println(md5);
System.out.println("time:"+ ((end-begin) /1000) +"s");
    } catch (IOExceptione) {
e.printStackTrace();
    }
  }

4、NIO读取

publicclassMD5FileUtil {
/*** 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校 验下载的文件的正确性用的就是默认的这个组合*/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) {
e.printStackTrace();
    }
  }
/*** 生成文件的md5校验值* @param file 文件路径* @return MD5码返回* @throws IOException*/publicstaticStringgetFileMD5(Filefile) throwsIOException {
StringencrStr="";
// 读取文件FileInputStreamfis=newFileInputStream(file);
// 当文件<2G可以直接读取if (file.length() <=Integer.MAX_VALUE) {
encrStr=getMD5Lt2G(file, fis);
    } else { // 当文件>2G需要切割读取encrStr=getMD5Gt2G(fis);
    }
fis.close();
returnencrStr;
  }
/*** 小于2G文件* * @param fis 文件输入流* @return* @throws IOException*/publicstaticStringgetMD5Lt2G(Filefile, FileInputStreamfis) throwsIOException {
// 加密码StringencrStr="";
FileChannelch=fis.getChannel();
MappedByteBufferbyteBuffer=ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
messagedigest.update(byteBuffer);
encrStr=bufferToHex(messagedigest.digest());
returnencrStr;
  }
/*** 超过2G文件的md5算法* * @param fileName* @param InputStream* @return* @throws Exception*/publicstaticStringgetMD5Gt2G(InputStreamfis) throwsIOException {
// 自定义文件块读写大小,一般为4M,对于小文件多的情况可以降低byte[] buffer=newbyte[1024*1024*4];
intnumRead=0;
while ((numRead=fis.read(buffer)) >0) {
messagedigest.update(buffer, 0, numRead);
    }
returnbufferToHex(messagedigest.digest());
  }
/*** * @param bt           文件字节流* @param stringbuffer 文件缓存*/privatestaticvoidappendHexPair(bytebt, StringBufferstringbuffer) {
// 取字节中高 4 位的数字转换, >>> 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同charc0=hexDigits[(bt&0xf0) >>4];
// 取字节中低 4 位的数字转换charc1=hexDigits[bt&0xf];
stringbuffer.append(c0);
stringbuffer.append(c1);
  }
privatestaticStringbufferToHex(bytebytes[], intm, intn) {
StringBufferstringbuffer=newStringBuffer(2*n);
intk=m+n;
for (intl=m; l<k; l++) {
appendHexPair(bytes[l], stringbuffer);
    }
returnstringbuffer.toString();
  }
privatestaticStringbufferToHex(bytebytes[]) {
returnbufferToHex(bytes, 0, bytes.length);
  }
/*** 判断字符串的md5校验码是否与一个已知的md5码相匹配* @param password  要校验的字符串* @param md5PwdStr 已知的md5校验码* @return*/publicstaticbooleancheckPassword(Stringpassword, Stringmd5PwdStr) {
Strings=getMD5String(password);
returns.equals(md5PwdStr);
  }
/*** 生成字符串的md5校验值* @param s* @return*/publicstaticStringgetMD5String(Strings) {
returngetMD5String(s.getBytes());
  }
/*** 生成字节流的md5校验值* @param s* @return*/publicstaticStringgetMD5String(byte[] bytes) {
messagedigest.update(bytes);
returnbufferToHex(messagedigest.digest());
  }
publicstaticvoidmain(String[] args) throwsIOException {
Stringpath="path";
Filebig=newFile(path);
longbegin=System.currentTimeMillis();
Stringmd5=getFileMD5(big);
longend=System.currentTimeMillis();
System.out.println("md5:"+md5);
System.out.println("time "+ (end-begin));
System.out.println("time:"+ ((end-begin) /1000) +"s");
  }
}

五、测试结果

image.png

600M以下:缓冲区 > NIO > MD5 > Apache

600M以上:Apache>缓冲区>NIO>MD5

六、总结


以上数据取样比较分散,可以采用均匀分布样本测试的结果可能更加特近真实效果,也可能得出一个转折点,可根据不同的数据量采用不同的生成模式,其效率有一定差别:


有兴趣的朋友私下可以进行多次测试,可得出更真实的结果


数据以小文件为主的,使用缓冲区生成MD5的方式效率更高,而到了G级别的文件,采用apache的生成方式更高效。上述结果仅供参考,实际情况下请各位根据需要采用不同的生成方式。如有更高效的生成方式,欢迎交流。


目录
相关文章
|
6月前
|
存储 Java 文件存储
|
1月前
|
存储 前端开发 Java
Java后端如何进行文件上传和下载 —— 本地版(文末配绝对能用的源码,超详细,超好用,一看就懂,博主在线解答) 文件如何预览和下载?(超简单教程)
本文详细介绍了在Java后端进行文件上传和下载的实现方法,包括文件上传保存到本地的完整流程、文件下载的代码实现,以及如何处理文件预览、下载大小限制和运行失败的问题,并提供了完整的代码示例。
279 1
|
1月前
|
Java
java 文件上传和下载
java 文件上传和下载
19 0
|
4月前
|
Java
java 文件上传 :MultipartFile 类型转换为file类型
java 文件上传 :MultipartFile 类型转换为file类型
158 9
|
4月前
|
缓存 前端开发 Java
在Java项目中实现高性能的文件上传下载
在Java项目中实现高性能的文件上传下载
|
4月前
|
前端开发 安全 Java
如何在Java中实现高效率的文件上传和下载
如何在Java中实现高效率的文件上传和下载
|
4月前
|
缓存 安全 Java
使用Java实现高性能的文件上传下载服务
使用Java实现高性能的文件上传下载服务
|
4月前
|
Java Apache UED
如何在Java中实现文件上传?
如何在Java中实现文件上传?
|
5月前
|
前端开发 Java 数据安全/隐私保护
如何在Java中实现文件上传和下载?
如何在Java中实现文件上传和下载?
|
5月前
|
Java
java使用多线程编写服务端与客户端文件上传程序
java使用多线程编写服务端与客户端文件上传程序