为什么将文件读入内存是Java中内存的4倍?

2022-09-04 23:42:18

我有以下代码,它读取以下文件,在每行末尾附加\r\n,并将结果放在字符串缓冲区中:

public InputStream getInputStream() throws Exception {
    StringBuffer holder = new StringBuffer();
    try{
        FileInputStream reader = new FileInputStream(inputPath);


        BufferedReader br = new BufferedReader(new InputStreamReader(reader));
        String strLine;
        //Read File Line By Line
        boolean start = true;
        while ((strLine = br.readLine()) != null)   {
            if( !start )    
                holder.append("\r\n");

            holder.append(strLine);
            start = false;
        }
        //Close the input stream
        reader.close();
    }catch (Throwable e){//this is where the heap error is caught up to 2Gb
      System.err.println("Error: " + e.getMessage());
    }


    return new StringBufferInputStream(holder.toString());
}

我尝试读取一个400Mb的文件,并将最大堆空间更改为2Gb,但它仍然给出了内存不足的堆异常。有什么想法吗?


答案 1

这可能与达到容量时的大小调整方式有关 — 这涉及创建一个比前一个大小大一倍的新阵列,然后将内容复制到新阵列中。再加上已经提出的关于Java中的字符存储为2个字节的观点,这肯定会增加您的内存使用量。StringBufferchar[]

要解决此问题,您可以创建一个具有足够容量的开始,前提是您知道文件大小(因此可以读取的近似字符数)。但是,请注意,如果随后尝试将此大数组转换为 .StringBufferStringBufferString

另一点:你通常应该支持它,因为它的操作更快。StringBuilderStringBuffer

您可以考虑实现自己的“CharBuffer”,例如使用char[]的a来避免昂贵的数组分配/复制操作。您可以使此类实现,并可能避免完全转换为 。另一个建议是更紧凑的表示:如果你正在阅读包含大量重复单词的英语文本,你可以阅读并存储每个单词,使用该函数可以显着减少存储空间。LinkedListCharSequenceStringString.intern()


答案 2

首先,Java字符串是UTF-16(即每字符2个字节),因此假设您的输入文件是ASCII或类似的一个字节/字符格式,那么将是输入数据大小的约2倍,加上每行的额外费用和任何额外的开销。假设StringBuffer中的存储开销非常低,则立即有大约800MB。holder\r\n

我还可以相信,您的文件的内容被缓冲了两次 - 一次在I / O级别,一次在BufferedReader中。

但是,为了确定,最好看看堆上的实际内容 - 使用像HPROF这样的工具来准确查看您的内存去了哪里。

就解决这个问题而言,我建议您一次处理一行,在添加行终止后写出每行。这样,您的内存使用量应与一行的长度成正比,而不是整个文件。


推荐