【问题标题】:Using Hashmap with set to read large file (20 giga) in Java — Java heap space在 Java 中使用带有 set 的 Hashmap 读取大文件(20 giga)——Java 堆空间
【发布时间】:2013-11-27 18:04:49
【问题描述】:

我正在尝试读取文件并将共享相同第一个令牌(readId)的行保存在一组(字符串)中。每个集合都是我的 hashmap 的一部分 >.

我已经将堆增加到 32 giga,也从 string.split 移动到 StringTokenizer,但我仍然遇到此错误:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOfRange(Arrays.java:2694)
    at java.lang.String.<init>(String.java:203)
    at java.lang.String.substring(String.java:1913)
    at java.util.StringTokenizer.nextToken(StringTokenizer.java:352)
    at java.util.StringTokenizer.nextElement(StringTokenizer.java:407)
    at Simple1_BootStrap.createMapSet(Simple1_BootStrap.java:68)
    at Simple1_BootStrap.main(Simple1_BootStrap.java:206)

以前,“内存不足错误”是由这一行产生的:

Set<String> s =new TreeSet<String>();

产生错误的代码是:

Map<String,Set<String>> map2 = new HashMap<String,Set<String>>();

    try{          
          BufferedReader br = new BufferedReader(new FileReader(filename)); 

          String strLine;
          String readId; 
          while ((strLine = br.readLine()) != null)   {
              alignment ++;
              StringTokenizer stringTokenizer = new StringTokenizer(strLine);

              readId = stringTokenizer.nextElement().toString();  

              if(map2.containsKey(readId)) {
                    Set<String> s = map2.get(readId);
                    s.add(strLine);
                    map2.put(readId, s);
                  }
                  else {
                      Set<String> s =new TreeSet<String>();
                      s.add(strLine);
                      map2.put(readId, s);
                  }
          }

          br.close();         
                      }catch (Exception e){//Catch exception if any
              System.err.println("Error: " + e.getMessage());
          }

我将这些行放在一个集合中,因为我需要在我的哈希图中随机选择条目并读取关联的集合以创建一个类似于输入文件的文件。

有人可以建议另一种方法来避免“内存不足错误”吗?

谢谢。

【问题讨论】:

  • 你真的需要所有的内存吗?对于这么大的东西,我更倾向于使用磁盘密钥存储,比如 berkeley db。
  • 尝试将所有这些加载到内存中可能不是最好的方法。如果我们忽略这一点:您的计算机中有多少物理 RAM?
  • Imo,不要使用内存来存储这么大的容量。考虑一个数据库。
  • 我用的服务器64G左右
  • FatalError 和 Taylor,您对数据库的看法是对的,因为我也在考虑它,但我担心:(1)性能和(2)分布,因为这是我可能喜欢的应用程序的一部分使用简单的安装包分发

标签: java


【解决方案1】:

当您读取一个字符串时,您应该期望它使用的内存是文件中的 2-4 倍。这是因为每个字符使用两个字节,但每个 String 对象 + char[] 使用大约 80 个字节的内存,例如一个 4 个字符的字符串大约使用 88 个字节。

将其添加到 HashMap 时,每条记录需要大约 100 个字节。

简而言之,假设您的主内存比这多得多,我会尝试至少 100 GB 的堆。


解决方案:

如果你没有这么多的记忆,我建议你重新考虑你的方法。例如。您可以对文件进行内存映射,使其根本不在堆上,并使用 Trove 集合按索引引用您的数据,而不使用索引对象。

【讨论】:

  • 谢谢彼得。实际上,我在服务器上有 256 Giga 内存,但在某个时间点几乎没有 64 Giga 可用。您能否解释一下 Trove 集合的使用,或者发送一个指向好的资源的指针?我很感谢你们的投入,因为在过去的两周里,我只是在寻找克服这个问题的方法。谢谢
  • Trove (trove.starlight-systems.com) 允许您拥有一个原始的键或值。这减少了内存消耗。如果源文本不在堆上,这也减少了内存消耗,因为字节数不变并且使用很少的堆。
【解决方案2】:

不管将所有内容加载到内存中是否明智,String.substring() 保留了对 Java 7 最新版本之前的 Java 版本的原始(更大)字符串的引用。因此,您可能会坚持使用 很多比你想象的更多的内存。详情请见this question/answer。

使用String(String) 构造函数从StringTokenizer 结果构建一个新字符串将缓解这种情况,升级到最近的Java 7 运行时也是如此。

【讨论】:

  • 我喜欢你所说的“无论智慧如何……”+1。另一个 +1 是给 Frank Zappa 的参考,但我只能给 1。
  • 我也在想同样的事情,但惊讶地发现这不是 OP 的情况。您可以在 OOME 的堆栈跟踪中看到子字符串正在分配一个新数组。我不知道他们最终在 Oracle 库中改变了这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-21
  • 1970-01-01
  • 2015-02-22
  • 2012-03-26
  • 2013-06-28
  • 2013-05-03
  • 1970-01-01
相关资源
最近更新 更多