【问题标题】:'Big dictionary' implementation in JavaJava中的“大字典”实现
【发布时间】:2014-09-29 20:11:55
【问题描述】:

我正在进行一个 Java 项目,该项目将使用单词的“大字典”。 “字典”是指分配给字符串的某些数字(整数)。 “大”是指 100 MB 大小的文件。我想出的第一个解决方案可能是最简单的。在初始化时,我读入了整个文件并创建了一个大的 HashMap,稍后将用于查找字符串。

有没有一种无需在初始化时读取整个文件的有效方法?也许不是,但如果文件真的很大,让我们按照可用 RAM 的顺序说呢?所以基本上我正在寻找一种方法来有效地在存储在内存中的大型字典中查找内容。

到目前为止,感谢您的回答,因此我意识到我的问题可以更具体。正如您可能已经猜到的那样,该应用程序与文本挖掘有关,特别是以稀疏向量的形式表示文本(尽管有些人有其他创造性的想法:))。所以使用的关键是能够在字典中查找字符串,尽可能快地获取它们的键。只要优化了字符串查找时间,“读取”字典文件或将其索引到数据库中的初始开销并不重要。同样,让我们​​假设字典大小很大,与可用 RAM 的大小相当。

【问题讨论】:

  • 您可以读取特定字节大小的文件,将其存储在HashMap 对象中,然后将该对象作为字节流对象存储在您的硬盘上。重复直到你读完整个文件。
  • @Mohammad 这并不能真正解决输入对象大于可用内存的用例。归根结底,您最终会得到一个 HashMap,其中包含太多无法容纳的对象。
  • Memory-mapped files 可能会有所帮助。查看FileChannel.map(),您可以从RandomAccessFile 获得FileChannel
  • 啊,我明白你的意思了。在这种情况下,数据检索很困难。
  • Trie (en.wikipedia.org/wiki/Trie) 将是此用例的(节省空间的)高效数据结构。

标签: java performance dictionary


【解决方案1】:

考虑非复制模式下的ChronicleMap (https://github.com/OpenHFT/Chronicle-Map)。它是一个堆外 Java Map 实现,或者从另一个角度来看,是一个超轻量级的 NoSQL 键值存储。

开箱即用对您的任务有什么用处:

  • 通过内存映射文件持久保存到磁盘(参见 Michał Kosmulski 的评论)
  • 延迟加载(磁盘页面仅按需加载)-> 快速启动
  • 如果您的数据量大于可用内存,操作系统会自动取消映射很少使用的页面。
  • 多个 JVM 可以使用同一个映射,因为堆外内存在操作系统级别共享。如果您在类似 map-reduce 的框架中进行处理,则很有用,例如。 G。 Hadoop。
  • 字符串以 UTF-8 格式存储,-> 如果字符串主要是 ASCII,则可节省约 50% 的内存(如 maaartinus 所述)
  • intlong 值仅占用 4(8) 个字节,就像您有原始专用地图实现一样。
  • 每个条目的内存开销非常小,远低于标准HashMapConcurrentHashMap
  • 如果您已经需要或将来打算并行化文本处理,则可以通过锁条化实现良好的可配置并发性。

【讨论】:

  • 哇。 +1 来自我!就个人而言,这非常酷,如果他不想提交到数据库,这似乎与他正在寻找的完全一样。我很好奇这种“堆外”实现的缺点是什么,这是我第一次听说它们。谢谢你教会了我一些真正了不起的东西! :0)
  • @DevarshDesai 阻止 ChronicleMap 成为真正的灵丹妙药的是键/值应该在每个查询上进行编组/解组。对于Strings,不幸的是,它带来了最严重的开销,因为UTF-8 String 转换非常复杂。但是,如果字符串数据可以存储为byte[],则开销要低得多,并且对于原始键/值几乎没有开销。如果键/值是几个原始(或另一个数据对象)字段的非常简单的数据对象,则还可以通过子类化Byteable 接口来避免 ser/deser 开销。
  • @maaartinus 我同意trie,缺点是它应该被实现,不像哈希表。我不知道好的开源实现,特别是堆外。 (如果你知道一些,请在你的答案中指出它们。)嗯,有一个想法开始ChronicleTrie :)
  • @PeterLawrey 我相信,如果你组织得当,你永远不会错过超过一页:靠近根的部分将留在内存中,你只需要一页为尾巴。恕我直言,当内存紧张时,trie 可能很有用; trie 仍然适合,而HashMap 不适合(因为它大 3-10 倍?只是猜测)。然后你会很乐意用一个页面未命中来换取一些缓存未命中。
  • @tsotsi 字符串查找适度的密钥大小大约需要 200 到 300 ns。它可以在 1% 的时间内跳到 5 微秒,通常是由于 TLB 缓存未命中。
【解决方案2】:

如果你的数据结构是几百 MB 到几级 RAM,你最好不要在运行时初始化数据结构,而是使用支持 indexing 的数据库(现在大多数情况下都是这样) )。一旦文件变得如此之大并且遇到 JVM 的 -Xmx 设置,索引将成为确保最快检索文本的唯一方法之一。这是因为如果您的文件与您的最大大小设置一样大或远大于最大大小设置,您将不可避免地转到crash your JVM

至于必须在初始化时读取整个文件。您最终将不得不这样做,以便您可以有效地搜索和分析代码中的文本。如果您知道一次只搜索文件的某个部分,则可以实现lazy loading。如果没有,您不妨硬着头皮将整个文件加载到数据库中。如果您的代码执行的其他部分不依赖于此,您可以在此过程中实现parallelism

如果您有任何问题,请告诉我!

【讨论】:

  • 根据我的经验,数据库实际上并没有那么慢(使用 MongoDB,我什至无法分辨差异)事实上,如果你能够正确利用给定的工具,你可以让它们变得非常快通过数据库内部。我从未见过大小为 100 MB 的数据结构,他还提到了“RAM 的顺序”,那时我会亲自使用数据库,正如其他人所建议的那样。我同意在内存中拥有更多的东西会使事情变得更快,但我并没有假设这篇文章的作者会出去为这个问题购买更多的内存。
  • 你说的那么慢是什么意思?我发现this benchmark...当没有磁盘涉及时,访问时间为 50 微秒,这意味着慢了 3 个数量级。 SSD 的时间是 10 毫秒。 DB 中没有什么比 HashMap 更快,而且开销相当大。 单独使用 IPC 的成本远高于查找本身。
  • 我从不质疑 HashMap 会更快的事实。我要说的是,100 MB 就是很多内存。根据我的直觉和最佳实践,我永远不会使用 HashMap 来存储这么多内存。根据我使用垃圾收集、数据库内部等方面的经验,我不建议使用哈希图在堆上为哈希图的单个实例分配 100MB 以上的空间。不过不要误会,我完全尊重你的论点,事实上,如果他不想做 db,我认为 leventov 的 ChronicalMap 是最好的解决方案。
  • 那么我们同意。我不会害怕 GC,因为数据似乎永远不会改变。我必须尝试一次会发生什么。实际上,标准的HashMap 是内存猪,Guava 的ImmutableHashMap 也是如此,但是周围有很多CompactHashMap
  • 哈哈,是的,我们同意 :0) CompactHashMap, ChronicleMap 将是解决这个问题的好方法。感谢您的精彩讨论,走出去学到了很多东西:)希望您有美好的一天!
【解决方案3】:

如评论中所述,Trie 将为您节省大量内存。

您还应该考虑使用 bytes 而不是 chars,因为这样可以为纯 ASCII 文本或使用您的国家字符集(只要它不超过 256 个不同的字母)节省 2 倍。

乍一看,将这种低级优化与尝试结合起来毫无意义,因为它们的节点大小由指针决定。但是如果你想去低级,有办法。

所以使用的关键是能够在字典中查找字符串,尽可能快地获取它们的键。

然后忘记任何数据库,因为与 HashMaps 相比,它们非常慢。

如果它不适合内存,最便宜的解决方案通常是获取更多。否则,请考虑仅加载最常用的单词并为其他单词做一些较慢的操作(例如,内存映射文件)。


我被要求指出一个好的尝试实现,尤其是堆外。我不知道。

假设 OP 不需要可变性,尤其是键的可变性,这一切看起来都很简单。

我想,整个字典可以很容易地打包成一个 ByteBuffer。假设主要是 ASCII 和一些位黑客,一个箭头每个箭头标签字符需要 1 个字节,子指针需要 1-5 个字节。子指针将是相对的(即当前节点和子节点之间的差异),这将使它们中的大多数在存储在 base 128 encoding 时适合单个字节。

我只能猜测总内存消耗,但我会说,每个单词

【讨论】:

    【解决方案4】:

    听起来太大了,无法存储在内存中。要么将其存储在关系数据库中(简单,并且在哈希上带有索引,速度快),要么存储在 NoSQL 解决方案中,例如 Solr(学习曲线小,非常快)。

    虽然 NoSQL 非常快,但如果您真的想调整性能,并且有些条目的查找频率远高于其他条目,请考虑使用有限大小的缓存来保存最近使用的(例如)10000 次查找。

    【讨论】:

      猜你喜欢
      • 2023-03-12
      • 1970-01-01
      • 2023-04-10
      • 1970-01-01
      • 2016-09-17
      • 1970-01-01
      • 1970-01-01
      • 2017-06-26
      • 1970-01-01
      相关资源
      最近更新 更多