【算法】哈希摘要算法,CRC冗余算法,MD摘要算法,HashMaker源码分享

效果图

《【算法】哈希摘要算法,CRC冗余算法,MD摘要算法,HashMaker源码分享》
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);
    }
}

结果:
单文件:
《【算法】哈希摘要算法,CRC冗余算法,MD摘要算法,HashMaker源码分享》

超大文件:(21.4GB)
《【算法】哈希摘要算法,CRC冗余算法,MD摘要算法,HashMaker源码分享》

多文件,多线程:
《【算法】哈希摘要算法,CRC冗余算法,MD摘要算法,HashMaker源码分享》

字符串:
《【算法】哈希摘要算法,CRC冗余算法,MD摘要算法,HashMaker源码分享》

    原文作者:哈希算法
    原文地址: https://blog.csdn.net/a4019069/article/details/79394911
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞