效果圖
JavaFX部分的代碼就不發了,網上關於java圖形化界面的開發教程太少,而且開發有一定的難度(B站只有4個視頻教程,還有一個看着感覺不錯的講的是印度咖喱味的英語),接下來就只分享核心算法
算法部分用了java的很多新特性,Stream,集合工廠,這兩個新特性完全把for循環消滅在一行代碼中,簡化了項目
源碼和工具包用法什麼的,請前往github,https://github.com/ZDG-Kinlon/HashMaker
純JAVA基本變量的代碼實現,待資料收集完畢後更新
至於每個版本支持哪些摘要,可以參考給出的算法遍歷 http://bbs.csdn.net/topics/80316868
Algorithm.java
package cn.hash;
public interface Algorithm {
//冗餘算法
String CRC32 = "CRC32";//java 6
String CRC32C = "CRC32C";//java 9
//MD摘要
String MD2 = "MD2";//java 6
String MD5 = "MD5";//java 6
//SHA1摘要
/*String SHA = "SHA";*/
String SHA_1 = "SHA-1";//java 6
//SHA2摘要
String SHA_224 = "SHA-224";//java 8
String SHA_256 = "SHA-256";//java 6
String SHA_384 = "SHA-384";//java 6
String SHA_512 = "SHA-512";//java 6
String SHA_512_224 = "SHA-512/224";//java 9
String SHA_512_256 = "SHA-512/256";//java 9
//SHA3摘要
String SHA3_224 = "SHA3-224";//java 9
String SHA3_256 = "SHA3-256";//java 9
String SHA3_384 = "SHA3-384";//java 9
String SHA3_512 = "SHA3-512";//java 9
}
Method.java
計算摘要所需的方法都在這裏,使用Lambda表達式,將方法傳遞給控制類,在控制類中傳入參數調用進行計算
方法分爲初始化參數、計算、結果整理三步驟,由於大型文件在計算時,需要分段截取字節然後進行計算,計算過程中必須按照一定順序進行偏移,所以不支持多線程,在多線程的情況下,不確定哪個線程會先執行,導致順序錯誤,生成錯誤的摘要值
package cn.hash;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.zip.CRC32;
import java.util.zip.CRC32C;
import java.util.zip.Checksum;
public class Method {
//待計算的任務集合
private Set<String> typeSet;
//遍歷需要計算指定摘要類型的set集合,key=摘要類型,value=對應摘要類型的摘要對象
private Map<String, MessageDigest> mapMD = new HashMap<>();
//遍歷需要計算指定冗餘類型的set集合,key=冗餘類型,value=對應冗餘類型的冗餘對象
private Map<String, Checksum> mapCS = new HashMap<>();
public Runnable getMethod1() {
return method1;
}
public BiConsumer<byte[], Integer> getMethod2() {
return method2;
}
public Function<Boolean, Map<String, String>> getMethod3() {
return method3;
}
public Method(Set<String> typeSet) {
this.typeSet = typeSet;
}
//初始化方法的Lambda表達式
private Runnable method1 = () -> {
//創建摘要對象集合
typeSet.forEach(type -> {
switch (type.toUpperCase()) {
case "CRC32":
//crc32算法
mapCS.put(type, new CRC32());
break;
case "CRC32C":
//crc32c算法
mapCS.put(type, new CRC32C());
break;
default:
try {
mapMD.put(type, MessageDigest.getInstance(type));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
//不支持的算法跳過
mapMD.put(type, null);
}
break;
}
});
};
//計算方法
private BiConsumer<byte[], Integer> method2 = (a, b) -> {
typeSet.forEach(type -> {
switch (type.toUpperCase()) {
case "CRC32":
case "CRC32C":
//冗餘算法
mapCS.get(type).update(a, 0, b);
break;
default:
//摘要算法,沒有得到對象的直接跳過
if (mapMD.get(type) != null) {
//摘要值生成,分段更新
mapMD.get(type).update(a, 0, b);
}
break;
}
});
};
//結果方法的Lambda表達式
private Function<Boolean, Map<String, String>> method3 = (a) -> {
//結果的map集合,key=類型,value=值
Map<String, String> resultMap = new HashMap<>();
typeSet.forEach(type -> {
String resultStr = null;
switch (type.toUpperCase()) {
case "CRC32":
case "CRC32C":
resultStr = dec2hex(mapCS.get(type).getValue(), a);
break;
default:
if (mapMD.get(type) != null) {
resultStr = bts2Hex(mapMD.get(type).digest(), a);
}
break;
}
resultMap.put(type, resultStr);
});
return resultMap;
};
/**
* 任意進制的數字轉換[2-36]
*
* @param num 待轉換的數字
* @param fromType 待轉換數字的進制類型
* @param toType 目標數字的進制類型
* @param isLowerCase 目標大小寫控制
* @return 字符串數字
* @deprecated 執行效率原因,不到萬不得已不推薦使用
*/
@Deprecated
public String numTranslate(String num, int fromType, int toType, boolean isLowerCase) {
num = new BigInteger(num, fromType).toString(toType);
return isLowerCase ? num.toLowerCase() : num.toUpperCase();
}
/**
* 十進制轉十六進制
*
* @param num 十進制待轉換的數字
* @param isLowerCase 目標大小寫控制
* @return 十六進制的數字
*/
public String dec2hex(Long num, boolean isLowerCase) {
String str = Long.toHexString(num);
return isLowerCase ? str.toLowerCase() : str.toUpperCase();
}
/**
* 將字節數組轉換爲十六進制字符串的結果
*
* @param bytes 摘要字節數組
* @param isLowerCase 目標結果大小寫
* @return 32位的十六進制摘要結果,如果需要16位的MD5摘要,substring(8, 24)截取即可
*/
public String bts2Hex(byte[] bytes, boolean isLowerCase) {
List<Character> table;
if (isLowerCase) {
table = List.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');
} else {
table = List.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
}
int length = bytes.length;
char[] temp = new char[length << 1];
int i = 0;
int var = 0;
while (i < length) {
temp[var++] = table.get((240 & bytes[i]) >>> 4);
temp[var++] = table.get(15 & bytes[i++]);
}
return new String(temp);
}
}
MsgDigestFile.java
計算單個文件的摘要計算類,構造方法傳入需要計算的摘要類型的Set集合,大小寫輸出方式,File對象,調用hash方法即可返回Map集合,key爲摘要算法類型,value爲摘要值
緩衝區的設置的小了,節省內存,讀取操作會增加,效率會降低,設置大了,內存會佔用很多,但是讀取操作會減少,一口氣全部加載到內存條然後完成計算是最佳效率,但是偏移最大隻能爲2GB(int範圍限制),所以超過2G的文件還是別想能全部加載到內存中,分段讀取到內存中計算是可行
預留了多線程方法,因爲有返回值,沒有使用實現Runnable接口的方式來實現多線程,使用線程池會更容易管理線程
package cn.hash;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
public class MsgDigestFile implements Callable<Map<String, String>> {
//方法對象
private Method method;
private Map<String, String> map;
private StringBuffer sb;
private int fileCacheSize = 1048576;//1MB
private boolean isLowerCase;
//文件
private File file;
//文件大小,字節
private long fileSize;
//剩餘未讀取字節數
private long surplusFileSize;
public MsgDigestFile(Set<String> set, boolean isLowerCase, File file) {
this.isLowerCase = isLowerCase;
this.file = file;
this.sb = new StringBuffer();
this.method = new Method(set);
surplusFileSize = fileSize = file.length();
sb.append("文件:").append(file.getAbsolutePath()).append("\n");
sb.append("大小:").append(file.length()).append(" 字節\n");
}
/**
* 線程池方法
*
* @return 執行結果
* @throws Exception
*/
@Override
public Map<String, String> call() throws Exception {
return hash();
}
/**
* 單線程方法
*
* @return
*/
public Map<String, String> hash() {
//創建緩衝區
byte[] bytes = new byte[fileCacheSize];
//初始化方法對象
method.getMethod1().run();
//創建文件輸入流,自動關閉資源
try (FileInputStream fis = new FileInputStream(file)) {
//讀取文件字節到緩存,在緩存中進行計算
while (fis.read(bytes) != -1) {
//計算偏移量,bytes中有效的未計算的字節長度
int offset = surplusFileSize > fileCacheSize ? fis.available() != 0 ? fileCacheSize : (int) surplusFileSize : (int) surplusFileSize;
//計算所有的任務
method.getMethod2().accept(bytes, offset);
//更新剩餘未讀取的字節
surplusFileSize -= offset;
}
//生成結果
return map = method.getMethod3().apply(isLowerCase);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 結果集合
*
* @return
*/
public Map<String, String> getMap() {
return map;
}
@Override
public String toString() {
map.forEach((k, v) -> sb.append(k).append(":").append(v).append("\n"));
return sb.toString();
}
/**
* 文件字節總數
*
* @return
*/
public long getFileSize() {
return fileSize;
}
/**
* 剩餘未讀取的字節數
*
* @return
*/
public long getSurplusFileSize() {
return surplusFileSize;
}
/**
* 設置緩衝區大小,字節,默認1MB
*
* @param fileCacheSize
*/
public void setFileCacheSize(int fileCacheSize) {
if (fileCacheSize > 0) {
this.fileCacheSize = fileCacheSize;
}
}
/**
* 獲取文件對象
*
* @return
*/
public File getFile() {
return file;
}
}
MsgDigestFiles.java
基於上面的類,拓展出多文件的摘要計算類,由於每個文件使用的是一個MsgDigestFile對象,不存在同步的問題,完全可以使用線程來提高效率,使用的是生成固定個數線程的線程池,這個個數根據文件來決定,所有文件一起同時啓動爭搶CPU資源進行計算,讓CPU滿負荷
package cn.hash;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MsgDigestFiles {
//基礎變量
private Set<String> set;
private Map<File, Map<String, String>> resultMap;
private StringBuffer sb;
private int fileCacheSize = 1048576;//1MB
private boolean isLowerCase;
//文件集合
private List<File> fileList;
//文件個數
private int fileCount;
//文件總大小,字節
private long filesSize;
public MsgDigestFiles(Set<String> set, boolean isLowerCase, List<File> fileList) {
this.set = set;
this.isLowerCase = isLowerCase;
this.fileList = fileList;
this.fileCount = fileList.size();
this.resultMap = new HashMap<>();
this.sb = new StringBuffer();
}
/**
* 多線程方法
*/
public Map<File, Map<String, String>> hash() {
filesSize = 0;
ExecutorService threadPool = Executors.newFixedThreadPool(fileCount);
List<MsgDigestFile> msgDigestFileList = new ArrayList<>();
Map<File, Future<Map<String, String>>> fileFutureMap = new HashMap<>();
fileList.forEach(file -> {
filesSize += file.length();
MsgDigestFile msgDigestFile = new MsgDigestFile(set, isLowerCase, file);
msgDigestFile.setFileCacheSize(fileCacheSize);
msgDigestFileList.add(msgDigestFile);
});
try {
msgDigestFileList.forEach(msgDigestFile -> fileFutureMap.put(msgDigestFile.getFile(), threadPool.submit(msgDigestFile)));
} finally {
threadPool.shutdown();
}
fileFutureMap.forEach((file, mapFuture) -> {
try {
resultMap.put(file, mapFuture.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
return resultMap;
}
@Override
public String toString() {
resultMap.forEach((file, map) -> {
sb.append("文件:").append(file.getAbsolutePath()).append("\n").append("大小:").append(file.length()).append("\n");
map.forEach((k, v) -> sb.append(k).append(":").append(v).append("\n"));
});
return sb.toString();
}
public long getFilesSize() {
return filesSize;
}
}
MsgDigestString.java
以上都是文件的摘要計算,接下來是字符串的摘要計算,由於字符串長度不會太長(最大理論支持到2GB,再大的只能存到文本文件裏,然後用上面的方法去處理),不進行多線程方法的實現,也不借助緩衝區,這個類只需要把字符串用指定的字符集拆分爲字節數組,傳入進行計算即可獲得結果
package cn.hash;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.Set;
public class MsgDigestString {
//方法對象
private Method method;
//基礎變量
private Map<String, String> map;
private StringBuffer sb;
private boolean isLowerCase;
//字符串
private String string;
//字符編碼類型
private String charset = "utf-8";
public MsgDigestString(Set<String> set, boolean isLowerCase, String string) {
this.isLowerCase = isLowerCase;
this.string = string;
this.sb = new StringBuffer();
this.method = new Method(set);
sb.append("文本:").append(string).append("\n");
sb.append("字數:").append(string.length()).append(" 字節\n");
}
public Map<String, String> hash() {
//初始化方法對象
method.getMethod1().run();
//創建字符流,自動關閉資源
try {
method.getMethod2().accept(string.getBytes(charset), string.length());
//生成結果
return this.map = method.getMethod3().apply(isLowerCase);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
map.forEach((k, v) -> sb.append(k).append(":").append(v).append("\n"));
return sb.toString();
}
/**
* 字符串編碼,缺省爲默認爲utf-8,不識別的字符集默認utf-8
*
* @param charset
*/
public void setCharset(String charset) {
try {
"".getBytes(charset);
this.charset = charset;
} catch (UnsupportedEncodingException e) {
this.charset = "utf-8";
}
}
}
最後就是調用示例
package cn.demo;
import cn.hash.MsgDigestFile;
import cn.hash.MsgDigestFiles;
import cn.hash.Algorithm;
import org.junit.Test;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class FileDemo {
/**
* 一個文件執行的示例
*/
@Test
public void fileDemo1() {
//1.創建文件對象
File file = new File("D:\\1.png");
//2.準備需要執行的方法集合,支持的類型在Algorithm接口中
Set<String> set = Set.of(
Algorithm.CRC32, Algorithm.CRC32C,
Algorithm.MD2, Algorithm.MD5,
Algorithm.SHA_1,
Algorithm.SHA_224, Algorithm.SHA_256, Algorithm.SHA_384, Algorithm.SHA_512, Algorithm.SHA_512_224, Algorithm.SHA_512_256,
Algorithm.SHA3_224, Algorithm.SHA3_256, Algorithm.SHA3_384, Algorithm.SHA3_512
);
//3.創建摘要對象,傳入摘要類型,大小寫輸出,文件
MsgDigestFile msgDigestFile = new MsgDigestFile(set, false, file);
//5.設置緩衝區的大小,單位字節,默認1MB,一次性讀取1MB,分段處理大型文件
msgDigestFile.setFileCacheSize(10485760);//10MB
msgDigestFile.getFileSize();//文件的大小,單位字節,long
msgDigestFile.getSurplusFileSize();//剩餘未讀取的字節大小,單位字節,long
//6.執行,接收結果,key=算法類型,value=計算的摘要值
Map<String, String> map = msgDigestFile.hash();
//7.輸出結果
map.forEach((key, value) -> System.out.println(key + ":" + value));
String string = msgDigestFile.toString();//字符串的結果,包括文件路徑,文件大小,和各文件摘要結果信息(無序),換行輸出
System.out.println(string);
}
/**
* 多文件,多線程的示例
*/
@Test
public void fileDemo2() {
//1.創建文件集合
List<File> fileList = List.of(
new File("D:\\1.png"),
new File("D:\\2.png"),
new File("D:\\3.png")
);
//2.準備需要執行的方法集合,支持的類型在Algorithm接口中
Set<String> set = Set.of(
Algorithm.CRC32, Algorithm.CRC32C,
Algorithm.MD2, Algorithm.MD5,
Algorithm.SHA_1,
Algorithm.SHA_224, Algorithm.SHA_256, Algorithm.SHA_384, Algorithm.SHA_512, Algorithm.SHA_512_224, Algorithm.SHA_512_256,
Algorithm.SHA3_224, Algorithm.SHA3_256, Algorithm.SHA3_384, Algorithm.SHA3_512
);
//3.創建多線程的摘要對象,傳入摘要類型,大小寫輸出,文件list集合
MsgDigestFiles msgDigestFiles = new MsgDigestFiles(set, false, fileList);
//4.獲取結果
Map<File, Map<String, String>> map = msgDigestFiles.hash();
//5.輸出結果
map.forEach((f, m) -> {
System.out.println("文件:" + f.getAbsolutePath());
System.out.println("大小:" + f.length());
m.forEach((k, v) -> System.out.println(k + ":" + v));
});
System.out.println("======");
String string = msgDigestFiles.toString();//字符串的結果,包括文件路徑,文件大小,和各文件摘要結果信息(無序),換行輸出
System.out.println(string);
}
}
結果:
單文件:
超大文件:(21.4GB)
多文件,多線程:
字符串: