【问题标题】:java: need to increase performance of checksum calculationjava:需要提高校验和计算的性能
【发布时间】:2011-05-22 23:34:45
【问题描述】:

我正在使用以下函数来计算文件的校验和:

public static void generateChecksums(String strInputFile, String strCSVFile) {
    ArrayList<String[]> outputList = new ArrayList<String[]>();
    try {
        MessageDigest m = MessageDigest.getInstance("MD5");
        File aFile = new File(strInputFile);
        InputStream is = new FileInputStream(aFile);

        System.out.println(Calendar.getInstance().getTime().toString() + 
                    " Processing Checksum: " + strInputFile);

        double dLength = aFile.length();
        try {
            is = new DigestInputStream(is, m);
            // read stream to EOF as normal...
            int nTmp;
            double dCount = 0;
            String returned_content="";
            while ((nTmp = is.read()) != -1) {
                dCount++;
                if (dCount % 600000000 == 0) {
                    System.out.println(". ");
                } else if (dCount % 20000000 == 0) {
                    System.out.print(". ");
                }
            }
            System.out.println();
        } finally {
            is.close();
        }
        byte[] digest = m.digest();
        m.reset();
        BigInteger bigInt = new BigInteger(1,digest);
        String hashtext = bigInt.toString(16);
        // Now we need to zero pad it if you actually / want the full 32 chars.
        while(hashtext.length() < 32 ){
            hashtext = "0" + hashtext;
        }
        String[] arrayTmp = new String[2];
        arrayTmp[0] = aFile.getName();
        arrayTmp[1] = hashtext;
        outputList.add(arrayTmp);
        System.out.println("Hash Code: " + hashtext);
        UtilityFunctions.createCSV(outputList, strCSVFile, true);
    } catch (NoSuchAlgorithmException nsae) {
        System.out.println(nsae.getMessage());
    } catch (FileNotFoundException fnfe) {
        System.out.println(fnfe.getMessage());
    } catch (IOException ioe) {
        System.out.println(ioe.getMessage());
    }
}

问题是循环读取文件真的很慢:

while ((nTmp = is.read()) != -1) {
    dCount++;
    if (dCount % 600000000 == 0) {
        System.out.println(". ");
    } else if (dCount % 20000000 == 0) {
        System.out.print(". ");
    }
}

一个 3 GB 的文件从一个位置复制到另一个位置不到一分钟,计算需要一个多小时。我可以做些什么来加快速度,还是应该尝试使用 shell 命令等不同的方向?

更新:感谢棘轮怪胎的建议,我将代码更改为快得离谱的代码(我猜要快 2048 倍...):

byte[] buff = new byte[2048];
while ((nTmp = is.read(buff)) != -1) {
    dCount += 2048;
    if (dCount % 614400000 == 0) {
        System.out.println(". ");
    } else if (dCount % 20480000 == 0) {
        System.out.print(". ");
    }
}

【问题讨论】:

  • 这个想法是为了表明标准输出的进度。这是我模拟命令行 ftp 客户端的“散列”行为的方式。 dcount % 60000000 执行 println 与 print。

标签: java file-io checksum


【解决方案1】:

使用缓冲区

byte[] buff = new byte[2048];
while ((nTmp = is.read(buff)) != -1)
{
     dCount+=ntmp;
     //this logic won't work anymore though
     /*
     if (dCount % 600000000 == 0)
     {
         System.out.println(". ");
     }
     else if (dCount % 20000000 == 0)
     {
         System.out.print(". ");
     }
     */
}

编辑:或者如果您不需要这些值,请执行

while(is.read(buff)!=-1)is.skip(600000000);

nvm 显然DigestInputStream 的实现者很愚蠢,在发布之前没有正确测试所有内容

【讨论】:

  • DigestInputStream 不会覆盖 skip(),因此摘要器不会处理跳过的字节。
  • 显然您没有阅读 DigestInputStream 的 Javadoc,它没有说 skip 会更新摘要。因愚蠢言论而被否决。
  • 伙计,它没有说 anything 关于跳过,他们也没有检查构造摘要中的空指针,让您弄清楚为什么您在在其他地方包装流
  • 没错,所以 skip() 的行为是继承的,这与所有三个 read() 方法的行为不同,它们记录在案以更新摘要。
【解决方案2】:

你试过删除 println 的吗?我想所有的字符串操作可能会消耗大部分处理!

编辑:我没有读清楚,我现在意识到它们很少被输出,我会收回我的答案,但我想这并不是完全无价的:-p(对不起!)

【讨论】:

  • println 只出现一小部分时间......如果有任何影响性能的条件逻辑。
  • +1:考虑控制台输出。在这种情况下,它不会是唯一的性能增强,但许多程序员没有意识到写入控制台时有多少开销。它会显着降低应用程序的速度。
  • 投了反对票,因为“所有字符串操作”每两千万次迭代才发生一次。
【解决方案3】:

问题是 System.out.print 使用得太频繁了。每次调用都必须创建新的 String 对象,而且开销很大。

改用 StringBuilder 类或其线程安全的模拟 StringBuffer。

StringBuilder sb = new StringBuilder();

每次你需要添加一些东西时,都可以这样调用:

sb.append("text to be added");

稍后,当您准备打印时:

system.out.println(sb.toString());

【讨论】:

    【解决方案4】:

    坦率地说,您的代码存在几个问题,使其运行缓慢:

    1. 就像棘轮怪胎说的那样,磁盘读取必须被缓冲,因为 Java read() 可能会在没有自动缓冲的情况下转换为操作系统 IO 调用,所以一个 read() 是 1 个系统调用!!! 如果您使用数组作为缓冲区或BufferedInputStream,操作系统通常会执行得更好。更好的是,您可以使用 nio 将文件映射到内存中,并以操作系统可以处理的速度尽可能快地读取它。

    2. 您可能不相信,但dCount++; 计数器可能已经使用了很多周期。我相信即使是最新的 Intel Core 处理器,也需要几个时钟周期才能完成 64 位浮点加法。你最好为这个计数器使用一个 long 。 如果此计数器的唯一目的是显示进度,您可以利用 Java 整数溢出而不会导致错误的事实,并在 char 类型包装为 0(即每 65536 次读取)时提前显示进度。

    3. 下面的字符串填充也是低效的。您应该使用StringBuilderFormatter

      while(hashtext.length()

    4. 尝试使用分析器来查找代码中更多的效率问题

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多