当前位置:   article > 正文

java文件上传判重姿势浅谈_com.twmacinta.util.md5

com.twmacinta.util.md5

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

二、应对:

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

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

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

三、方案

开发语言:java  JDK 1.8 IDE:eclipse

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

四、代码实现

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

  1. @Test
  2. public void test1()
  3. String path = "your file path";
  4. try {
  5. long begin = System.currentTimeMillis();
  6. String md5 = DigestUtils.md5Hex(new FileInputStream(path));
  7. long end = System.currentTimeMillis();
  8. System.out.println(md5);
  9. System.out.println("time:" + ((end - begin) / 1000) + "s");
  10. } catch (FileNotFoundException e) {
  11. e.printStackTrace();
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. }
  15. }

2、自定义缓冲区实现

  1. @Test
  2. public void test2() {
  3. String path ="file path";
  4. long begin = System.currentTimeMillis();
  5. BigInteger bi = null;
  6. try {
  7. byte[] buffer = new byte[8192 * 10];
  8. int len = 0;
  9. MessageDigest md = MessageDigest.getInstance("MD5");
  10. File f = new File(path);
  11. FileInputStream fis = new FileInputStream(f);
  12. while ((len = fis.read(buffer)) != -1) {
  13. md.update(buffer, 0, len);
  14. }
  15. fis.close();
  16. byte[] b = md.digest();
  17. bi = new BigInteger(1, b);
  18. } catch (NoSuchAlgorithmException e) {
  19. e.printStackTrace();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }
  23. String md5 = bi.toString(16);
  24. long end = System.currentTimeMillis();
  25. System.out.println(md5);
  26. System.out.println("time:" + ((end - begin) / 1000) + "s");
  27. }

3、com.twmacinta.util.MD5

  1. @Test
  2. public void test3() {
  3. String path ="file path";
  4. long begin = System.currentTimeMillis();
  5. try {
  6. String md5 = MD5.asHex(MD5.getHash(new File(path)));
  7. long end = System.currentTimeMillis();
  8. System.out.println(md5);
  9. System.out.println("time:" + ((end - begin) / 1000) + "s");
  10. } catch (IOException e) {
  11. e.printStackTrace();
  12. }
  13. }

4、NIO读取

  1. public class MD5FileUtil {
  2. /**
  3. * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校 验下载的文件的正确性用的就是默认的这个组合
  4. */
  5. protected static char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
  6. 'f' };
  7. protected static MessageDigest messagedigest = null;
  8. static {
  9. try {
  10. messagedigest = MessageDigest.getInstance("MD5");
  11. } catch (NoSuchAlgorithmException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. /**
  16. * 生成文件的md5校验值
  17. * @param file 文件路径
  18. * @return MD5码返回
  19. * @throws IOException
  20. */
  21. public static String getFileMD5(File file) throws IOException {
  22. String encrStr = "";
  23. // 读取文件
  24. FileInputStream fis = new FileInputStream(file);
  25. // 当文件<2G可以直接读取
  26. if (file.length() <= Integer.MAX_VALUE) {
  27. encrStr = getMD5Lt2G(file, fis);
  28. } else { // 当文件>2G需要切割读取
  29. encrStr = getMD5Gt2G(fis);
  30. }
  31. fis.close();
  32. return encrStr;
  33. }
  34. /**
  35. * 小于2G文件
  36. *
  37. * @param fis 文件输入流
  38. * @return
  39. * @throws IOException
  40. */
  41. public static String getMD5Lt2G(File file, FileInputStream fis) throws IOException {
  42. // 加密码
  43. String encrStr = "";
  44. FileChannel ch = fis.getChannel();
  45. MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
  46. messagedigest.update(byteBuffer);
  47. encrStr = bufferToHex(messagedigest.digest());
  48. return encrStr;
  49. }
  50. /**
  51. * 超过2G文件的md5算法
  52. *
  53. * @param fileName
  54. * @param InputStream
  55. * @return
  56. * @throws Exception
  57. */
  58. public static String getMD5Gt2G(InputStream fis) throws IOException {
  59. // 自定义文件块读写大小,一般为4M,对于小文件多的情况可以降低
  60. byte[] buffer = new byte[1024 * 1024 * 4];
  61. int numRead = 0;
  62. while ((numRead = fis.read(buffer)) > 0) {
  63. messagedigest.update(buffer, 0, numRead);
  64. }
  65. return bufferToHex(messagedigest.digest());
  66. }
  67. /**
  68. *
  69. * @param bt 文件字节流
  70. * @param stringbuffer 文件缓存
  71. */
  72. private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
  73. // 取字节中高 4 位的数字转换, >>> 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
  74. char c0 = hexDigits[(bt & 0xf0) >> 4];
  75. // 取字节中低 4 位的数字转换
  76. char c1 = hexDigits[bt & 0xf];
  77. stringbuffer.append(c0);
  78. stringbuffer.append(c1);
  79. }
  80. private static String bufferToHex(byte bytes[], int m, int n) {
  81. StringBuffer stringbuffer = new StringBuffer(2 * n);
  82. int k = m + n;
  83. for (int l = m; l < k; l++) {
  84. appendHexPair(bytes[l], stringbuffer);
  85. }
  86. return stringbuffer.toString();
  87. }
  88. private static String bufferToHex(byte bytes[]) {
  89. return bufferToHex(bytes, 0, bytes.length);
  90. }
  91. /**
  92. * 判断字符串的md5校验码是否与一个已知的md5码相匹配
  93. * @param password 要校验的字符串
  94. * @param md5PwdStr 已知的md5校验码
  95. * @return
  96. */
  97. public static boolean checkPassword(String password, String md5PwdStr) {
  98. String s = getMD5String(password);
  99. return s.equals(md5PwdStr);
  100. }
  101. /**
  102. * 生成字符串的md5校验值
  103. * @param s
  104. * @return
  105. */
  106. public static String getMD5String(String s) {
  107. return getMD5String(s.getBytes());
  108. }
  109. /**
  110. * 生成字节流的md5校验值
  111. * @param s
  112. * @return
  113. */
  114. public static String getMD5String(byte[] bytes) {
  115. messagedigest.update(bytes);
  116. return bufferToHex(messagedigest.digest());
  117. }
  118. public static void main(String[] args) throws IOException {
  119. String path ="path";
  120. File big = new File(path);
  121. long begin = System.currentTimeMillis();
  122. String md5 = getFileMD5(big);
  123. long end = System.currentTimeMillis();
  124. System.out.println("md5:" + md5);
  125. System.out.println("time " + (end - begin));
  126. System.out.println("time:" + ((end - begin) / 1000) + "s");
  127. }
  128. }

五、测试结果

方式/时间

304KB

31.2MB

69.5MB

600MB

3.09G

apache

37ms

489ms

1121ms

8987ms

45699ms

缓冲区

4ms

134ms

292ms

9393ms

45993ms

md5

19ms

173ms

338ms

9021ms

48427ms

nio去读

22ms

165ms

320ms

9815ms

45347ms

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

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

六、总结

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/871558
推荐阅读
相关标签
  

闽ICP备14008679号