【问题标题】:Read file in android app takes too much time在android应用程序中读取文件需要太多时间
【发布时间】:2012-11-27 11:13:35
【问题描述】:

我正在为 Android 编写一个 Hangman 应用程序,但遇到了一些加载问题。 当我的应用程序启动时,我会生成一个从 txt 文件中挑选的随机单词。问题是这个文件很重:它几乎有 360'000 个单词(4000 kB),因此选择一个单词需要 10 到 20 秒的时间,我知道这是因为我每次读取文件并选择一个单词我点击生成。

如果我在应用程序启动后读取文件并将其内容放入字符串数组中会是一件好事吗?我真的不知道数组是否可以处理这么多。 否则什么是好的模式?

感谢阅读。

【问题讨论】:

  • 发布您的拣货代码,那里可能有很多需要优化的地方。 (一个选项,例如:使它成为一个 sqlite db,在启动时复制它,随机选择顺序,另一个选项:使你的行固定宽度,并跳过(长度 * rand())来选择你的单词)
  • 您的数组将是 8000 kb(每个 char 为 2 个字节)+ 360000 * 字符串对象的几个字节,您可以将其四舍五入到大约 10Mb,这是考虑保留在记忆。另外,加载仍然需要 10-20 秒。

标签: java android file-io


【解决方案1】:

将其存储为 CSV,(逗号分隔值)。打开一个读取流,寻找一个随机位置并读取一小块字符,直到你点击',' 两次。现在,从你读过的字符串中提取逗号之间的那个词。您也可以使用自定义分隔符,例如“#”或“|”而不是逗号。

更新:使用RandomAccessFile

Update2: readLine() 让这更容易,如果你每行存储一个单词。 IE。以\n分隔

【讨论】:

  • 为什么不使用现有的分隔符(大概是'\n')?将 '\n' 替换为 ',' 并不会真正改变任何内容,而且会使文件更难编辑。除此之外,我喜欢寻找位置 N 然后向前扫描以寻找单词开头的想法。 (这意味着在统计上,长词比短词更有可能被挑选出来,但这在这里可能并不重要。)
【解决方案2】:

在 AsyncTask 中执行代码以解析/加载 json/db 格式的内容以提高速度。我加载 5000 行,每行约 400 个字符。没有 AsyncTask 需要更长的时间。

    private class YourTask extends AsyncTask<String, Void, String> {
        @Override
        protected String doInBackground(String... s) {

            //Here you have to make the loading / parsing tasks
            //Don't call any UI actions here. For example a Toast.show() this will couse Exceptions
            // UI stuff you have to make in onPostExecute method

        }

        @Override
        protected void onPreExecute() {
            // This method will called during doInBackground is in process
            // Here you can for example show a ProgressDialog
        }

        @Override
        protected void onPostExecute(Long result) {
            // onPostExecute is called when doInBackground finished
            // Here you can for example fill your Listview with the content loaded in doInBackground method

        }

}

要执行你只需要调用:

new YourTask().execute("");

您可以在此处了解有关 AsyncTasks 的更多信息:

AsyncTask developer Guide

【讨论】:

  • 对不起,我的回复迟了,我终于提出了这个解决方案,但我仍然有一个问题:我想放一个进度条来通知用户文件正在加载但如何处理因为我反序列化一个对象?我的想法是启动一个线程,在序列化尚未准备好时增加一个计数器。
【解决方案3】:

您可以(并且可能最好)创建一个单词数据库并在其中查询随机单词。你可以先阅读Saving Data in SQL Databases

【讨论】:

    【解决方案4】:

    文件读取是一项昂贵的操作,需要这么长时间是正常的。您可能应该创建一组文件而不是一个文件,并且每次启动应用程序时,您都可以从中选择一个随机文件和一个随机单词。这应该会减少所需的时间。

    此外,您可以将单词名称作为应用资源存储在 XML 文件中,该资源将进入 /res/values/ 文件夹。您可以创建字符串数组资源并轻松地从代码中获取它,而不必担心打开/关闭/读取文件。这非常简单。但是在这里将所有单词拆分为不同的 xml 文件(或至少是字符串数组组)对您来说再次很重要。在这里阅读更多。 http://developer.android.com/guide/topics/resources/string-resource.html#StringArray

    或者另一种方法是通过从文件中读取所有内容来将其存储到您的数据库中。这样,应用程序的首次启动只需要时间。您可以在使用时显示诸如“正在准备 DB...”之类的消息或类似的内容。

    【讨论】:

      【解决方案5】:

      这里需要注意的一些事情:

      1. 如果您的应用程序从 SD 卡读取数据,则 SD 卡的质量实际上是一个因素 - 根据您使用的 SD 卡的类别,有不同的读/写速度 - 查一下。

      2. 您提到每次单击“生成”时都会在文件中随机选择一行 - 因为您可以确定用户会多次单击“生成”,所以读取该文件一次是个好主意,将内容存储在某处(sqlite),下次需要获取随机词时,从存储的数据中获取。这样,您将从外部存储读取的次数(在移动标准中是一项繁重的任务)限制为每次应用程序启动一次。

      3. 您甚至可以在文件上放置一个标记,让您知道内容是否已更改。从随机字符串到当前日期到 md5 校验和的任何内容都应该让您知道您不需要再次读取该文件,因为您之前已经阅读过它并将内容存储在您的 sqlite 数据库中。你想把它放在文件的第一行。

      4. 既然其他人都在告诉您考虑写入 sqlite db 甚至复制 .db 文件本身(您可以单独生成),那么选择一个随机项目将是微不足道的。我宁愿警告你在你的数据库中写入 360,000 个条目。使用默认的 ContentProvider insert(Uri, ContentValues) 方法将起作用,但如果这比实际解析文件本身花费的时间更长,我不会感到惊讶。这里的技巧是使用编译语句来覆盖 ContentProvider 的 bulkInsert(Uri, ContentValues[]) 的实现。默认实现只多次调用insert 方法,因此每次插入都会打开和关闭您的SqliteDatabase 对象。你想要做的是在顶部调用beginTransaction,有一个for循环inserts ContentValues中的每个项目setTransactionSuccessful,然后调用endTransaction

      【讨论】:

        【解决方案6】:

        将整个文件读入字符串数组不是一个好策略,因为这会消耗大量内存。一个简单的解决方案是将您的大文件拆分为几个单独的文件(使用文件名的模式,如 words1.txt、words2.txt 等)。绘制单词时,您将首先选择文件,然后从文件中选择实际的单词。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-01
          • 1970-01-01
          • 2016-08-06
          • 2012-07-20
          • 1970-01-01
          相关资源
          最近更新 更多