Java Git是如何如此快速地计算SHA哈希的?

Java Git是如何如此快速地计算SHA哈希的?,java,git,scala,hash,sha,Java,Git,Scala,Hash,Sha,我知道git很快,但直到最近我才发现它真的可以有多快 在我的一个项目中,我试图计算一个大文件的SHA-256散列(82MB,850k行),计算它花费了一分钟(包括散列和其他一些小操作) 即使使用SHA-1,我也花了30多秒的时间,而git似乎只用了一两秒钟就完成了 我正在使用java的SecurityAPI结合文件的所有行来计算Scala中的哈希 val lines = Source.fromFile(filePath, "UTF-8").getLines().toList MessageDig

我知道git很快,但直到最近我才发现它真的可以有多快

在我的一个项目中,我试图计算一个大文件的SHA-256散列(82MB,850k行),计算它花费了一分钟(包括散列和其他一些小操作)

即使使用SHA-1,我也花了30多秒的时间,而git似乎只用了一两秒钟就完成了

我正在使用java的
Security
API结合文件的所有行来计算Scala中的哈希

val lines = Source.fromFile(filePath, "UTF-8").getLines().toList
MessageDigest.getInstance("SHA-256")
.digest(lines.mkString("\n").getBytes).map("%02x".format(_)).mkString
那么,Git是如何做到这么快的,或者更重要的问题是,为什么我的方法这么慢


编辑:对于那些不熟悉scala语法的人,
lines
将把文件的所有行都放在一个
List
中,并且
mkString
方法返回一个包含列表中所有元素的字符串,该字符串与给定的分隔符组合在一起

哈希计算在编译时重定向到中的特定实现。底层平台可提供优化(例如,汇编程序或机器相关的C编码)散列例程。当然,您的Java实现可能也可能不提供这样的例程


如果平台没有自己的实现,Git可以在大内存块上工作,并且仍然有一些手动调整,并使用架构和编译器
ifdef
s内联
asm
s.

重新发布我之前的评论(扩展)

你要做的是:

  • 读取字节
  • 将它们转换为字符
  • 将字符流拆分为行
  • 将这些行存储到列表中
  • 再次将这些行连接成一个字符串
  • 再次获取其字节
  • 计算这些字节的散列
  • 步骤2-6似乎没有必要。我建议从您的初始
    FileInputStream
    中分块(例如,4k)读取字节,并将它们提供给
    MessageDigest
    进行更新。这将只执行步骤1和7

    InputStream is = new FileInputStream(fileName);
    byte[] buffer = new byte[4096];
    while (true) {
        int read = is.read(buffer);
        if (read < 0) {
            break;
        }
        md.update(buffer, 0, read);
    }
    is.close(); // better be done in finally
    

    Git无疑要快一些,但SHA-1的30秒就没那么好了

    所以我用java运行了一个测试:

    public static void main(String[] args) throws Exception{
        long startTime = System.currentTimeMillis();
    
        byte[] bytes = createSha1(new File("src\\main\\resources\\200mb_file.zip"));
        System.out.println(new String(bytes));
    
        long endTime = System.currentTimeMillis();
        long duration = (endTime - startTime);
        System.out.format("Duration: %dms\n", duration);
    }
    
    private static byte[] createSha1(File file) throws Exception  {
        MessageDigest digest = MessageDigest.getInstance("SHA-1");
        InputStream fis = new FileInputStream(file);
        int n = 0;
        byte[] buffer = new byte[8192];
        while (n != -1) {
            n = fis.read(buffer);
            if (n > 0) {
                digest.update(buffer, 0, n);
            }
        }
        return digest.digest();
    }
    
    输出:

    Duration: 1531
    

    我猜是什么原因导致您的速度慢,是因为您正在将其输入列表,而不是直接将其用作流。

    您检查了Git的源代码吗?那应该是开始的地方。@JimGarrison,我试着去寻找它,但我没有找到实际进行哈希运算的确切代码。另外,我对C代码不是很熟悉,我不认为我能很好地理解它。。。很多额外的工作。难道您没有尝试直接从
    InputStream
    (以4k块为单位)读取字节,并将它们提供给
    MessageDigest
    实例进行更新吗?这可能要快得多。按照@RomanPuchkovskiy的建议读取原始字节可能更接近Git的做法。Git不关心行,它将散列文本、二进制,这对Git来说都是一样的。计算差异时,它只关心线条。@RomanPuchkovskiy。我之所以把它列入清单是因为我需要在中间的另一个操作。但是,这种解析是否会让它变得如此缓慢?我将尝试你建议的方法,并将看到性能。看起来额外的过程使它慢了很多。我试着像你建议的那样读取字节并将它们交给hasher,现在大约需要30秒,以前大约是65秒。虽然速度是原来的两倍,但30秒似乎还是很长的时间,尤其是当Git能够以如此快的速度完成任务时。我会继续寻找更好的方法,谢谢你的回答。这很奇怪,我尝试在同一个179Mb文件上用上面的代码计算sha1哈希,结果花费了
    real 0m1.192s
    。比sha1sum慢两倍,但不是一个数量级。我已经多次重复这两个测试,让系统缓存等等。一些探查器可能会帮助您找出问题。一个有趣的事实:清空FS缓冲区+缓存后,java程序会更快:它需要1.4秒,而sha1sum需要1.7秒。哇。比本地性能更快,这很有趣!
    Duration: 1531