Android将单词插入ArrayList,内存不足

use*_*570 5 java android out-of-memory

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

文件大小为386KB和380KB,每个文件少于20万个字。

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();
}
Run Code Online (Sandbox Code Playgroud)

该程序在运行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字节分配),然后发生FATAL EXCEPTION,内存耗尽。

如何装入这些文件而又不会得到这么大的堆?

在MyApp内部:

private ArrayList<String> dictionary = new ArrayList<String>();
public void addToDictionary(String word) {
    dictionary.add(word);
}
Run Code Online (Sandbox Code Playgroud)

DNA*_*DNA 1

不管任何其他问题/错误,ArrayList对于这种存储来说都是非常浪费的,因为随着不断增长的 ArrayList 耗尽空间,它会使其底层存储数组的大小加倍。因此,您的存储空间可能有近一半被浪费了。如果您可以将存储数组或 ArrayList 预先设置为正确的大小,那么您可能会节省大量成本。

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

我预计您的输入需要不到 2MB 的空间来存储文本本身(请记住,Java 在内部使用 UTF-16,因此每个字符通常需要 2 个字节),但 String 对象引用可能需要 1.5MB 的开销,再加上 1.5MB String 长度的开销,并且偏移量和哈希码可能一次又一次相同(看看String.java)...虽然 24MB 的堆听起来仍然有点过多,但如果您接近的话,它也相差不远了- 不幸的 ArrayList 大小调整的影响加倍。

事实上,与其猜测,不如测试一下?以下代码-Xmx24M在停止之前运行并获取大约 560,000 个 6 字符字符串(在 Java SE 7 JVM 上,64 位)。它最终爬升至 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);
    }
Run Code Online (Sandbox Code Playgroud)

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

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