效果图
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)
多文件,多线程:
字符串: