0

我有以下 JAVA 类可以从包含多行制表符分隔字符串的文件中读取。示例行如下所示:

GO:0085044      GO:0085044      GO:0085044

代码读取每一行并使用 split 函数将三个子字符串放入一个数组中,然后将它们放入一个两级哈希中。

public class LCAReader {
    public static void main(String[] args) {
        Map<String, Map<String, String>> termPairLCA = new HashMap<String, Map<String, String>>();
        File ifile = new File("LCA1.txt");
        try {
            BufferedReader reader = new BufferedReader(new FileReader(ifile));
            String line = null;
            while( (line=reader.readLine()) != null ) {
                String[] arr = line.split("\t");
                if( termPairLCA.containsKey(arr[0]) ) {
                    if( termPairLCA.get(arr[0]).containsKey(arr[1]) ) {
                        System.out.println("Error: Duplicate term in LCACache");
                    } else {
                        termPairLCA.get(arr[0]).put(new String(arr[1]), new String(arr[2]));
                    }
                } else {
                    Map<String, String> tempMap = new HashMap<String, String>();
                    tempMap.put( new String(arr[1]), new String(arr[2]) );
                    termPairLCA.put( new String(arr[0]), tempMap );
                }
            }
            reader.close();
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}

当我运行程序时,运行一段时间后出现以下运行时错误。我注意到内存使用量不断增加。

线程“主”java.lang.OutOfMemoryError 中的异常:在 java.util.regex.Pattern.(Pattern.java:1150) 处的 java.util.regex.Pattern.compile(Pattern.java:1469) 处超出 GC 开销限制java.util.regex.Pattern.compile(Pattern.java:840) 在 java.lang.String.split(String.java:2304) 在 java.lang.String.split(String.java:2346) 在 LCAReader.main (LCAReader.java:17)

输入文件差不多2G,我运行程序的机器有8G内存。我还尝试了 -Xmx4096m 参数来运行程序,但这没有帮助。所以我猜我的代码中有一些内存泄漏,但我找不到它们。

谁可以帮我这个事?提前致谢!

4

3 回答 3

3

没有内存泄漏;你只是想存储太多的数据。2GB 的文本将占用 4GB 的 RAM 作为 Java 字符;加上每个 String 对象开销大约有 48 个字节。假设文本是 100 个字符行,大约还有另一个 GB,总共 5GB——我们甚至还没有计算Map.Entry对象!您需要至少(保守地说)6GB 的 Java 堆才能在您的数据上运行该程序,甚至可能更多。

您可以做一些简单的事情来改善这一点。首先,失去new String()构造函数——它们毫无用处,只会让垃圾收集器更努力地工作。字符串是不可变的,因此您永远不需要复制它们。其次,您可以使用实习生池来共享重复的字符串——这可能有帮助,也可能没有帮助,具体取决于数据的实际外观。但是你可以尝试,例如,

tempMap.put(arr[1].intern(), arr[2].intern() );

这些简单的步骤可能会有很大帮助。

于 2012-04-15T03:02:03.320 回答
0

我没有看到任何泄漏,您只需要非常大量的内存来存储您的地图。有一个非常好的工具可以验证这一点:使用选项 - XX:+HeapDumpOnOutOfMemoryError进行堆转储并将其导入独立版本的Eclipse 内存分析器。它可以显示最大的保留对象和可能阻止垃圾收集器完成其工作的引用树。此外,诸如Netbeans Profiler之类的分析器可以为您提供许多有趣的实时信息(例如检查 String 和 Char 实例的数量)。

此外,将代码拆分为不同的类也是一个很好的做法,每个类都有不同的责任:一侧的“双键映射”类(TreeMap)和另一侧的“解析器”类,它应该使调试更容易.. .

将这个巨大的地图存储在 RAM 中绝对不是一个好主意……或者您需要使用一些较小的文件进行基准测试并推断以获得系统上所需的估计 RAM 以适应最坏的情况.. . 并将 Xmx 设置为适当的值。为什么不使用诸如 Berckley DB 之类的键值存储:比关系 DB 更简单,并且应该完全适合您对两级索引的需要。查看此帖子以了解商店的选择:键值存储建议

祝你好运

于 2012-04-15T18:31:02.457 回答
0

您可能不应该使用String.split和存储纯粹的信息,String因为这会动态生成大量String对象。

尝试使用char基于方法,因为您的格式似乎相当固定,因此您知道一行上不同数据点的确切索引。

如果您更多地进行实验,您可以尝试使用 NIO 支持的方法,该方法具有内存映射DirectByteBufferCharBuffer用于遍历文件的内存。在那里,您可以将不同数据点的 indizes 标记到标记对象中,并且仅String在需要时在过程中稍后加载真实数据。

于 2012-04-15T18:50:58.030 回答