我正在寻找一个用于存储大文本(大约一百万字)的Java数据结构,这样我就可以按索引获取一个单词(例如,获取531467个单词)。
String []或ArrayList的问题在于它们占用了太多内存 - 在我的环境中每个字大约有40个字节。
我想过使用一个String [],其中每个元素是一个10个单词的块,由一个空格连接。这样的内存效率更高 - 每个字大约20个字节;但访问速度要慢得多。
有没有更有效的方法来解决这个问题?
答案 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");
}
这在两个方面会更小:
我不会真的推荐这种方法,但是再次访问速度会慢一些,因为每次都需要创建一个新的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()后再次:
平均字长约为3个字符,大多数字符都是非ASCII字符,因此可能大约为6个字节。所以,实习生似乎接近最佳状态。这是有道理的,因为它是一系列单词,而且许多单词出现的次数不止一次。
答案 7 :(得分:0)
我可能会考虑使用一个固定大小的单词或某种索引的文件。带跳过的FileInputStream可以非常高效
答案 8 :(得分:0)
如果您有移动设备,则可以使用TIntArrayList,每个int值使用4个字节。如果每个单词使用一个索引,则需要几个MB。您也可以使用int[]
如果您有PC或服务器,这是一个微不足道的内存量。内存成本约为每GB 6英镑或每MB 1美分。