一种节省内存的大量单词

时间:2011-08-12 11:24:30

标签: java data-structures

我正在寻找一个用于存储大文本(大约一百万字)的Java数据结构,这样我就可以按索引获取一个单词(例如,获取531467个单词)。

String []或ArrayList的问题在于它们占用了太多内存 - 在我的环境中每个字大约有40个字节。

我想过使用一个String [],其中每个元素是一个10个单词的块,由一个空格连接。这样的内存效率更高 - 每个字大约20个字节;但访问速度要慢得多。

有没有更有效的方法来解决这个问题?

9 个答案:

答案 0 :(得分:3)

正如Jon Skeet已经提到的,40mb并不是太大。

但是你说你存的是一个文本,所以可能会有很多相同的字符串。 例如,停止使用“and”和“or”等词语。

您可以使用String.intern()[1]。这将汇集您的String并返回对已存在的String的引用。

intern()非常慢,所以你可以用HashMap替换它,它会为你做同样的技巧。

[1] http://download.oracle.com/javase/6/docs/api/java/lang/String.html#intern%28%29

答案 1 :(得分:2)

你可以看看使用memory mapping the data structure,但性能可能非常糟糕。

答案 2 :(得分:2)

将所有单词存储在一个字符串中:

class WordList {

    private final String content;
    private final int[] indices;

    public WordList(Collection<String> words) {
        StringBuilder buf = new StringBuilder();
        indices = new int[words.size()];
        int currentWordIndex = 0;
        int previousPosition = 0;
        for (String word : words) {
            buf.append(word);
            indices[currentWordIndex++] = previousPosition;
            previousPosition += word.length();
        }
        content = buf.toString();
    }

    public String wordAt(int index) {
        if (index == indices.length - 1) return content.substring(indices[index]);
        return content.substring(indices[index], indices[index + 1]);
    }

    public static void main(String... args) {
        WordList list = new WordList(Arrays.asList(args));
        for (int i = 0; i < args.length; ++i) {
            System.out.printf("Word %d: %s%n", i, list.wordAt(i));
        }
    }

}

除了它们包含的字符外,使用此解决方案(indices中的条目),每个字的开销为4个字节。使用wordAt检索单词将始终分配新字符串;您可以通过保存toString() StringBuilder而不是构建器本身来避免这种情况,尽管它在构造时会占用更多内存。

根据文字,语言等的类型,您可能需要一种能够更好地处理重复单词的解决方案(例如the one previously proposed)。

答案 3 :(得分:1)

一种选择是存储字节数组,而不是以UTF-8编码的文本:

byte[][] words = ...;

然后:

public String getWord(int index)
{
   return new String(words[index], "UTF-8");
}

这在两个方面会更小:

  • 每个字符串的数据是直接在byte []中,而不是具有几个整数成员的字符串和对单独的char []对象的引用
  • 如果您的文本主要是ASCII,那么对于那些ASCII字符,每个字符使用一个字节,您将受益于UTF-8

我不会真的推荐这种方法,但是再次访问速度会慢一些,因为每次都需要创建一个新的String。从根本上说,如果你需要一百万个字符串对象(所以你不想每次都支付娱乐惩罚),那么你将不得不使用内存来存放一百万个字符串对象......

答案 4 :(得分:1)

您可以创建如下数据结构:

  • List<string> wordlist
  • Dictionary<string, int> tsildrow // for reverse lookup while building the structure
  • List<int> wordindex

wordlist将包含所有(唯一)字词的列表, tsildrow会在wordlist中提供单词的索引,而wordindex会告诉您文本中特定索引的wordlist中的索引。

您可以按以下方式操作:

for word in text:
    if not word in tsildrow:
        wordlist.append(word)
        tsildrow.add(word, wordlist.last_index)
    wordindex.append(tsildrow[word])

这填补了您的数据结构。现在,要在索引531467找到这个词:

print wordlist[wordindex[531467]]

你可以像这样重现整个文本:

for index in wordindex:
    print wordlist[index] + ' '

除了,你仍然会有标点符号等问题......

如果你不再添加任何单词(即你的文字是稳定的),你可以删除tsildrow以释放一些内存,如果这是你的问题。

答案 5 :(得分:1)

-XX:+UseCompressedStrings
  

对字符串使用byte [],可以表示为纯ASCII。   (在Java 6 Update 21性能发布中引入)

http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

看起来像一篇有趣的文章: http://www.javamex.com/tutorials/memory/string_saving_memory.shtml

我听说绳索在存储大字符串的速度方面非常好,但不确定记忆性。但你可能想看一下。 http://ahmadsoft.org/ropes/ http://en.wikipedia.org/wiki/Rope_%28computer_science%29

答案 6 :(得分:1)

好的,我已经尝试了几个你的建议,这是我的结果(我在填充数组之前检查了(Runtime.getRuntime()。totalMemory() - Runtime.getRuntime()。freeMemory())并检查了填充数组和gc()后再次:

  • 原创(字符串数组):54字节/字(不是40,因为我错误地写了)
  • 我的解决方案(字符串数组,用空格分隔):
    • 每个字块2个字 - 36 b / w(但性能不可接受)
    • 每个字块10个字 - 18 b / w
    • 每个单词100字 - 14 b / w
  • 字节数组 - 40 b / w
  • char数组 - 36 b / w
  • HashMap,将字符串映射到自身,或将字符串映射到其索引 - 26 b / w
    • (不确定我是否正确实施了这个)
  • 实习生 - 10 b / w
  • 基线(空阵列) - 4 b / w

平均字长约为3个字符,大多数字符都是非ASCII字符,因此可能大约为6个字节。所以,实习生似乎接近最佳状态。这是有道理的,因为它是一系列单词,而且许多单词出现的次数不止一次。

答案 7 :(得分:0)

我可能会考虑使用一个固定大小的单词或某种索引的文件。带跳过的FileInputStream可以非常高效

答案 8 :(得分:0)

如果您有移动设备,则可以使用TIntArrayList,每个int值使用4个字节。如果每个单词使用一个索引,则需要几个MB。您也可以使用int[]

如果您有PC或服务器,这是一个微不足道的内存量。内存成本约为每GB 6英镑或每MB 1美分。