【问题标题】:Android: File Reading - OutOfMemory IssueAndroid:文件读取 - OutOfMemory 问题
【发布时间】:2013-02-08 00:20:33
【问题描述】:

我正在创建一个涉及从文件中读取数据的应用程序。该文件相对较大 (1.8 MB),正在从 onCreate 中的异步线程读取。应用程序第一次启动时,它加载得很好。但是,如果您单击返回按钮然后再次加载它,它会耗尽内存并崩溃(抛出 OutOfMemory 错误)。

如何让它使用尽可能少的内存和/或在完成后释放该内存?

文件读取代码(在异步类的doInBackground()方法中执行):

public ArrayList<String> createDataList() {
    dataList = new ArrayList<String>();
    BufferedReader br = null;
    try {
        br = new BufferedReader(new InputStreamReader(getAssets().open(
                    "text.txt")));
        String data;
        while ((data = br.readLine()) != null) {
            dataList.add(data);
        }                           
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            br.close(); // stop reading
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    return dataList;
}

编辑* 异步类:

private class loadData extends AsyncTask<Void, Void, ArrayList<String>> {

    @Override
    protected ArrayList<String> doInBackground(Void... arg0) {
        dataList = createDataList();
        return dataList;
    }

    protected void onPostExecute(ArrayList<String> result) {
        super.onPostExecute(result);
        // display first element in ArrayList in TextView as a test

    }

}

我已经尝试根据我想要如何组织数据来拆分文件并将每个文本文件中的数据存储到单独的ArrayList 中,但我也遇到了内存问题。我还将所有数据存储到一个“主”ArrayList 中,然后在该“主”上调用一个方法将数据添加到适当的ArrayList(从“主”中删除/清除数据)它复制了)。

关于如何简化和减少内存影响的任何想法?

编辑**

Logcat:

这是从您单击后退按钮然后再次加载活动时开始的。以下只是生成的消息之一(详细):

【问题讨论】:

  • 你需要这些数据做什么?方法返回后的dataList怎么办?
  • 你确定 DataList 是罪魁祸首吗?您能否添加 logcat,以便我们可以查找可能显示正在发生的事情的其他消息?
  • @fd。我将向用户显示数据。通过交互,用户将能够显示更多(见我的编辑)。
  • @DigCamara 我很确定考虑到我现在只有一个TextView 来显示ArrayList 的第一个元素作为测试以确保ArrayList 是越来越多。 :) 不过我会放一些 logcat(给我一分钟把它放在这里)。
  • @DigCamara 我添加了 logcat 消息。

标签: android android-assets android-memory


【解决方案1】:

您可以尝试在清单中添加 android:largeHeap="true",但 Android API-8 不支持它。据我了解,您正在读取数据并将其存储到堆内存中,这通常非常有限,其大小取决于您运行应用程序的设备。

您可能还想在这里调查:android - out of memory

【讨论】:

  • 我不完全理解该链接中的所有代码,但从我从接受的答案中得到的数据正在写入文件中。如果您从一个文件中读取然后写入另一个文件,这似乎会适得其反。还是我错了?要从您写入的文件中获取数据,您必须再次读取它。所以看起来你做了两倍以上的工作。同样,我以前从未遇到过内存问题,因为这是我制作的第一个涉及从文件读取数据的应用程序。
  • 我可以使用包含数千个 dataList.add(); 语句的长方法来避免从文件中读取。但是,这似乎不太实用,如果可以避免的话,我也不想这样做。
  • android:largeHeap 旨在为在大屏幕上需要大量 UI 的平板电脑应用程序提供更多堆内存。在关于内存使用的 Google I/O 演示文稿中,他们明确表示不赞成使用此功能来快捷地编写内存意识代码。
【解决方案2】:

首先,确保内存中没有数据的两个副本。您可以在开始创建新的 ArrayList 之前取消对旧 ArrayList 的引用,但您必须小心操作——首先调用 ArrayList.clear() 会更彻底。

其次,使用hprof 或 Eclipse MAT 等工具计算 ArrayList 占用了多少内存。如果它使用大量空间,您可能需要考虑更紧凑的数据表示。

所以...从代码 sn-p 来看,您似乎只是在使用字节到字符的转换从文件中读取一堆文本字符串。如果源材料是纯 UTF-8(即本质上是 ASCII),那么您将获得 UTF-16 的 2 倍扩展,加上分配 char[] 对象来保存它,加上包装它的 String 对象的大小,加上 ArrayList 中条目的开销。根据文件中平均字符串的长度,这可能是 1.8MB 的重要倍数。

避免这种情况的一种方法是将文件作为byte[] 读入内存,扫描它以找出每个字符串的开始位置,然后只保留一个整数数组以及每个字符串的起始偏移量。当您需要字符串 N 时,将其从 byte[] 解码为 String 并返回。这大大减少了您的开销。您可以通过不加载文件并根据需要仅读取单个字符串(使用RandomAccessFile)来进一步减少它,但这可能会减慢速度。

【讨论】:

  • 在将数据复制到正确的 ArrayLists 后,我会清除 ArrayList。文本文件采用 UTF-8 格式,字符串不超过 15 个。我不确定字节数组将如何工作(我以前从未以这种方式读取文件)。您能否详细说明一下它是如何工作的,也许(如果可以的话)提供与我所拥有的等价物?我会考虑如何让 RandomAccessFile 工作(如果可以的话),这取决于它是如何工作的。我也必须阅读它,因为我也没有使用过。 :)
  • RandomAccessFile 实际上看起来相当不错。老实说,在你提到它之前,我从未听说过它。我只需要能够检查/遍历文件中的字符串,直到找到满足条件的字符串;看起来这是可能的。
  • 你能提供一个 RandomAccessFile 的例子吗?另外,文件应该放在哪里以及如何从中读取? (我读到您不能将 RandomAccessFile 用于 assets 文件夹。)
  • 您可以从Context.getFilesDir() 找到您应用的私有数据目录(您的Activity 是一个上下文)。如果您将资产流中的数据复制到那里的文件中,则可以在该文件上使用RandomAccessFile。权衡的是,您现在使用 1.8MB 的内部磁盘存储,而不是使用 1.8MB 的内存来保存字节 [],并且访问单个字符串会更慢。
  • 我在 Linux 上,我使用的主要文本处理程序是 gedit。我可以将其保存为 Western 或 UTF-8。字符串范围为 4-15 个字符。可以提供一些关于如何将代码复制到内部磁盘存储然后从 RAF 访问它的代码吗?我真的不知道该怎么做。我在大学里在空闲时间自学这个。我并不真正关心从内部存储访问它的速度。我相信它对于我的目的来说足够快(除非它需要像 5 秒这样荒谬的东西才能得到一个字符串)。
【解决方案3】:

似乎您可能对 immutability of Strings 有点麻烦。

您为什么不尝试更改代码以便使用 StringBuilder ?当然,您必须更改不止一件事,但它与您的代码足够相似,并且不会很快填满您的内存。

【讨论】:

  • 会节省多少空间?我总是尽量让所有资源都尽可能轻松和简单(例如,我的背景总是有深色以节省电池)。如果用户点击后退按钮然后重新启动它,这是否也能解决问题?
  • 问题是:您创建的任何新字符串都会在堆上徘徊,直到......好吧,直到所有引用都被杀死。文档指出:“多个字符串可以共享相同的 char[],因为字符串是不可变的。substring(int) 方法总是返回一个共享其源字符串的支持数组的字符串。通常这是一种优化:需要更少的字符数组被分配,并且需要更少的复制。但这也可能导致不必要的堆保留。因此,根据定义,您使用字符串是在浪费内存空间。
  • 我想不出另一种使用字符串的方法,所以看起来 RandomAccessFile 可能有很好的机会,因为我只会在给定时间读取我需要的行。我还可以根据我希望文件的组织方式将文件拆分为单独的文件,并且我可以将文件名作为 RandomAccessFile 所在方法的参数传递。因此,我可以消除对对文件进行大量迭代。
  • StringBuilder 有一个使用字符串的构造函数,通常与字符串足够相似,因此您的更改相对较小。
猜你喜欢
  • 2011-09-26
  • 1970-01-01
  • 1970-01-01
  • 2011-11-23
  • 1970-01-01
  • 1970-01-01
  • 2017-11-30
  • 2014-07-14
相关资源
最近更新 更多