我写了一些简单的代码来阅读文本文件(> 1g)并对字符串进行一些处理。
但是,我必须处理Java堆空间问题,因为我尝试追加Strings(使用StringBuilder),它们在某些时候对内存使用有很大的影响。我知道我可以增加我的堆空间,e。 G。 '-Xmx1024',但我想在这里只使用很少的内存。我如何更改下面的代码来管理我的操作?
我仍然是Java新手,也许我在代码中犯了一些错误,这对你来说显而易见。
以下是代码段:
private void setInputData() {
Pattern pat = Pattern.compile("regex");
BufferedReader br = null;
Matcher mat = null;
try {
File myFile = new File("myFile");
FileReader fr = new FileReader(myFile);
br = new BufferedReader(fr);
String line = null;
String appendThisString = null;
String processThisString = null;
StringBuilder stringBuilder = new StringBuilder();
while ((line = br.readLine()) != null) {
mat = pat.matcher(line);
if (mat.find()) {
appendThisString = mat.group(1);
}
if (line.contains("|")) {
processThisString = line.replace(" ", "").replace("|", "\t");
stringBuilder.append(processThisString).append("\t").append(appendThisString);
stringBuilder.append("\n");
}
}
// doSomethingWithTheString(stringBuilder.toString());
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
if (br != null)br.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
以下是错误消息:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2367) at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:130) at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:114) at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:415) at java.lang.StringBuilder.append(StringBuilder.java:132) at Test.setInputData(Test.java:47) at Test.go(Test.java:18) at Test.main(Test.java:13)
答案 0 :(得分:1)
在这种情况下,您无法使用StringBuilder。它将数据保存在内存中。 我认为你应该考虑将结果保存到每一行的文件中。
即。使用FileWriter而不是StringBuilder。
答案 1 :(得分:1)
一般策略是设计你的应用程序,使其不需要在内存中保存整个文件(或者它的一大部分)。
取决于您的应用程序的功能:
doSomethingWithTheString(...)
而不是全部调用它们。但是如果你需要将整个文件放在内存中,那么你就是在摇滚和困难之间。
另一件需要注意的是,使用类似StringBuilder
的内存可能需要高达文件大小的6倍内存。它是这样的。
当StringBuilder
需要扩展其内部缓冲区时,它通过将char数组设置为当前缓冲区大小的两倍,并从旧模块复制到新缓冲区来实现此目的。此时,在缓冲区扩展开始之前,您分配的缓冲区空间是3倍。现在假设还有一个字符要附加到缓冲区。
如果文件是ASCII(或另一个8位字符集),StringBuilder
的缓冲区需要两倍的内存...因为它由char
而不是{{ 1}}值。
如果您对最终字符串中的字符数有一个很好的估计(例如从文件大小),则可以在创建byte
时通过提供容量提示来避免x3乘数。但是,你不能低估,“因为你只是略微低估了......”
您也可以使用面向字节的缓冲区(例如StringBuilder
)而不是StringBuilder ...然后使用ByteArrayOutputStream
/ ByteArrayInputStream
/ {{1}来读取它管道。
但最终,随着文件大小的增加,在内存中保存一个大文件并不会扩展。
答案 2 :(得分:1)
从你的例子中可以看出,一旦修改了你的巨大字符串,你将会怎么做。但是,由于您的修改似乎不跨越多行,我只是将修改后的数据写入新文件。
为了在FileWriter
周期之前创建并打开新的while
对象,请将stringBuffer
声明移至周期的开头并将stringBuffer
写入在周期结束时你的新文件。
另一方面,如果您需要组合来自不同行的数据,请考虑使用数据库。哪种取决于您的数据的性质。如果它具有类似记录的组织,您可以采用关系数据库,例如Apache Derby或MySQL,否则您可能会检出所谓的无SQL数据库,例如Cassandra或{{ 3}}
答案 3 :(得分:1)
你可以在不附加的情况下进行干运行,但计算总的字符串长度。
如果doSomethingWithTheString是顺序的,那么就会有其他解决方案。
您可以对字符串进行标记,从而减小尺寸。例如,Huffman压缩查找已经存在的读取char的序列,可能扩展表,然后产生表索引。 (开源的OmegaT翻译工具在一个地点使用这种策略作为令牌。)所以这取决于你想要做的处理。看到一种CSV的阅读字典似乎是可行的。
一般情况下,我会使用数据库。
P.S。您可以节省一半内存,将所有内容写入文件,然后将文件重新读取到一个字符串中。或者在文件上使用java.nio ByteBuffer,这是一个内存映射文件。
答案 4 :(得分:1)
方法doSomethingWithTheString()可能需要更改,以便它也接受InputStream。在读取原始文件内容并逐行转换时,您应该将转换后的内容逐行写入临时文件。然后,可以将该临时文件的输入流发送到doSomethingWithTheString()方法。可能需要将该方法重命名为doSomethingWithInputStream()。
答案 5 :(得分:0)
你确定文件中有一个行终止符吗?如果没有,你的while循环将继续循环并导致你的错误。如果是这样,可能值得一次尝试读取固定数量的字节,这样读者就无法无限增长。
答案 6 :(得分:0)
我建议使用Guavas FileBackedOutputStream。你获得了一个OutputStream的优势,它将占用磁盘io而不是主内存。当然,由于磁盘io访问速度会变慢,但是,如果你正在处理这么大的流,并且你无法将其分成更大的可管理大小,那么这是一个不错的选择。