Android向ArrayList中添加单词时出现内存不足问题

5

我有两个文件,一个包含长度3到6的单词字典,另一个包含长度为7的单词字典。这些单词存储在文本文件中,用换行符分隔。该方法加载文件并将其插入到ArrayList中,然后存储在应用程序类中。

文件大小分别为386KB和380KB,每个文件包含不到200k个单词。

private void loadDataIntoDictionary(String filename) throws Exception {
    Log.d(TAG, "loading file: " + filename);
    AssetFileDescriptor descriptor = getAssets().openFd(filename);
    FileReader fileReader = new FileReader(descriptor.getFileDescriptor());
    BufferedReader bufferedReader = new BufferedReader(fileReader);
    String word = null;

    int i = 0;

    MyApp appState = ((MyApp)getApplicationContext());

    while ((word = bufferedReader.readLine()) != null) {
        appState.addToDictionary(word);
        word = null;
        i++;
    }
    Log.d(TAG, "added " + i + " words to the dictionary");

    bufferedReader.close();
}

在运行2.3.3版本的模拟器上,64MB的SD卡程序崩溃。

使用logcat报告错误。

堆大小超过24 MB。然后我看到将目标GC堆从25.XXX减小到24.000 MB。

GC_FOR_MALLOC释放了0K,12%可用,外部1657k/2137K,暂停208ms。
GC_CONCURRENT释放了XXK,14%可用
分配24字节内存时内存不足,随后发生致命错误。

如何在不获取如此大的堆的情况下加载这些文件?

在MyApp中:

private ArrayList<String> dictionary = new ArrayList<String>();
public void addToDictionary(String word) {
    dictionary.add(word);
}

你为什么要这样做:word = null? - Amokrane Chentir
我试图确保gc知道如何释放这个单词。我只是把它作为最后的手段添加了进去。 - user1781570
请添加addToDictionary()函数的代码,它是查找内存泄漏最显然的地方。 - Dan Hulme
看起来 word=null 导致了一个无限循环。把它去掉然后运行。 - chrislhardin
@DanHulme 我在末尾添加了代码。 - user1781570
1个回答

1

除了其他问题/错误外,ArrayList 对于这种类型的存储可能非常浪费,因为随着 ArrayList 的增长,它会将其底层存储数组的大小加倍。因此,你的存储空间可能浪费了近一半。如果你可以预设一个正确大小的存储数组或 ArrayList,则可以获得显著的节省。

此外(戴上偏执数据清理帽子),请确保输入文件中没有额外的空格-如果需要,可以在每个单词上使用 String.trim(),或者先清理输入文件。但是,考虑到你提到的文件大小,我不认为这可能是一个重大问题。

我预计您的输入在存储文本本身时应该不到2MB(请记住,Java在内部使用UTF-16,因此每个字符通常需要2个字节),但是String对象引用可能会有1.5MB的开销,加上String长度的1.5MB开销,以及偏移量和哈希码的可能相同的额外开销(请查看String.java)...虽然24MB的堆听起来有点过多,但如果您遇到了不幸的ArrayList重新调整大小的近似加倍效果,那么也不远了。

实际上,与其猜测不如进行一次测试?在Java SE 7 JVM(64位)上使用-Xmx24M运行以下代码,在停顿之前可以达到约560,000个6个字符的字符串。最终它会爬升到大约580,000个字符串(我想会有很多GC抖动)。

    ArrayList<String> list = new ArrayList<String>();
    int x = 0;
    while (true)
    {
        list.add(new String("123456"));
        if (++x % 1000 == 0) System.out.println(x);
    }

所以我认为你的代码中没有错误——在Java中存储大量小字符串并不是很高效。对于上面的测试,由于所有开销(可能在32位和64位机器之间有所不同,并且还取决于JVM设置),每个字符需要超过7个字节!

如果存储字节数组的数组而不是字符串的ArrayList,则可能会获得稍微更好的结果。还有更有效的数据结构可用于存储字符串,例如Tries


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接