【问题标题】:Reading and writing huge files in java在java中读取和写入大文件
【发布时间】:2014-03-20 10:37:23
【问题描述】:

我的想法是制作一个小软件来读取文件(不能“自然”读取,但它包含一些图像),将其数据转换为十六进制,查找 PNG 块(一种标记位于 .png 文件的开头和结尾),并将生成的数据保存在不同的文件中(从十六进制取回后)。我在 Java 中这样做,使用这样的代码:

// out is where to show the result and file is the source
public static void hexDump(PrintStream out, File file) throws IOException {
    InputStream is = new FileInputStream(file);
    StringBuffer Buffer = new StringBuffer();

    while (is.available() > 0) {
        StringBuilder sb1 = new StringBuilder();

        for (int j = 0; j < 16; j++) {
            if (is.available() > 0) {
                int value = (int) is.read();
                // transform the current data into hex
                sb1.append(String.format("%02X ", value));
            }
        }

        Buffer.append(sb1);

        // Should I look for the PNG here? I'm not sure
    }
    is.close();
    // Print the result in out (that may be the console or a file)
    out.print(Buffer);

}

我确信在打开大文件时还有其他方法可以使用更少的“机器资源”来做到这一点。如果您有任何想法,请告诉我。谢谢!

这是我第一次发帖,如有错误,请帮我更正。

【问题讨论】:

  • 您的实际问题是什么?此外,过早的优化是万恶之源——先编写可维护的代码,然后再优化。
  • @Smutje 问题是我在大文件上试过这个,将十六进制数据复制到输出文件需要很长时间。关于可维护的代码,我应该在这里改进什么?我是 Java 编程的新手
  • 为什么要将整个文件转换为十六进制?如果您知道十六进制的 PNG 标记,则将其转换为二进制,以便您可以直接将其与文件进行比较。

标签: java file hex


【解决方案1】:

正如 Erwin Bolwidt 在 cmets 中所说,第一件事是不要转换为十六进制。如果由于某种原因必须转换为十六进制,请停止将内容附加到两个缓冲区,并始终使用 StringBuilder,而不是 StringBuffer。 StringBuilder 可以比 StringBuffer 快 3 倍。

另外,使用 BufferedReader 缓冲您的文件读取。使用FileInputStream.read() 一次读取一个字符非常慢。

【讨论】:

    【解决方案2】:

    执行此操作的一种非常简单的方法(可能非常快)是将整个文件读入内存(作为二进制数据,而不是作为十六进制转储),然后搜索标记。

    这有两个限制:

    • 它只处理长度不超过 2 GiB 的文件(Java 数组的最大大小)
    • 它需要大块内存 - 可以通过读取器更小的块来优化这一点,但这会使算法更加复杂

    执行此操作的基本代码如下:

    import java.io.File;
    import java.io.IOException;
    import java.nio.file.Files;
    
    public class Png {
    
        static final String PNG_MARKER_HEX = "abcdef0123456789"; // TODO: replace with real marker
        static final byte[] PNG_MARKER = hexStringToByteArray(PNG_MARKER_HEX);
    
        public void splitPngChunks(File file) throws IOException {
            byte[] bytes = Files.readAllBytes(file.toPath());
            int offset = KMPMatch.indexOf(bytes, 0, PNG_MARKER);
            while (offset >= 0) {
                int nextOffset = KMPMatch.indexOf(bytes, 0, PNG_MARKER);
                if (nextOffset < 0) {
                    writePngChunk(bytes, offset, bytes.length - offset);
                } else {
                    writePngChunk(bytes, offset, nextOffset - offset);
                }
                offset = nextOffset;
            }
        }
    
        public void writePngChunk(byte[] bytes, int offset, int length) {
            // TODO: implement - where do you want to write the chunks?
        }
    }
    

    我不确定这些 PNG 块标记是如何工作的,我在上面假设它们开始您感兴趣的数据部分,并且下一个标记开始数据的下一部分。

    标准 Java 中缺少两件事:将十六进制字符串转换为字节数组的代码和在另一个字节数组中搜索字节数组的代码。 两者都可以在各种 apache-commons 库中找到,但我将包括回答人们在 StackOverflow 上发布的早期问题的答案。您可以将这些逐字复制到 Png 类中,以使上述代码正常工作。

    Convert a string representation of a hex dump to a byte array using Java?

    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }
    

    Searching for a sequence of Bytes in a Binary File with Java

    /**
     * Knuth-Morris-Pratt Algorithm for Pattern Matching
     */
    static class KMPMatch {
        /**
         * Finds the first occurrence of the pattern in the text.
         */
        public static int indexOf(byte[] data, int offset, byte[] pattern) {
            int[] failure = computeFailure(pattern);
    
            int j = 0;
            if (data.length - offset <= 0)
                return -1;
    
            for (int i = offset; i < data.length; i++) {
                while (j > 0 && pattern[j] != data[i]) {
                    j = failure[j - 1];
                }
                if (pattern[j] == data[i]) {
                    j++;
                }
                if (j == pattern.length) {
                    return i - pattern.length + 1;
                }
            }
            return -1;
        }
    
        /**
         * Computes the failure function using a boot-strapping process, where the pattern is matched against itself.
         */
        private static int[] computeFailure(byte[] pattern) {
            int[] failure = new int[pattern.length];
    
            int j = 0;
            for (int i = 1; i < pattern.length; i++) {
                while (j > 0 && pattern[j] != pattern[i]) {
                    j = failure[j - 1];
                }
                if (pattern[j] == pattern[i]) {
                    j++;
                }
                failure[i] = j;
            }
    
            return failure;
        }
    }
    

    我修改了最后一段代码,使其可以在非零偏移量处开始搜索。

    【讨论】:

      【解决方案3】:

      读取文件一次一个字节将在这里花费大量时间。您可以将其提高几个数量级。您应该在 FileInputStream 周围使用 DataInputStream 周围的 BufferedInputStream,并使用 readFully. 一次读取 16 个字节

      然后处理它们,没有在十六进制之间进行转换,这在此处是完全没有必要的,然后通过@987654326 周围的BufferedOutputStream 将它们写入输出@ 而不是将整个文件连接到内存中并且必须一次将其全部写出来。 当然这需要时间,但那是因为它确实如此,而不是因为你必须这样做。

      【讨论】:

        猜你喜欢
        • 2011-07-27
        • 2013-03-25
        • 1970-01-01
        • 2015-05-29
        • 2013-01-06
        • 1970-01-01
        • 2015-07-15
        • 1970-01-01
        • 2012-12-29
        相关资源
        最近更新 更多