在Android应用中读取文件需要太长时间

3
我正在为Android编写一个Hangman应用程序,但我遇到了一些加载问题。当我的应用程序启动时,我从一个txt文件中随机选择一个单词。问题在于这个文件很重:它有近360,000个单词(4000 kB),因此每次点击“生成”按钮选取一个单词需要花费10至20秒的时间。我知道这是因为我每次点击“生成”按钮时都会读取文件并选取一个单词。
如果我在应用程序启动时只读取一次文件并将其内容放入字符串数组中,这会是一个好主意吗?我不确定数组是否能处理这么多数据。否则,什么是正确的模式?
感谢您的阅读。

发布你的挑选代码,那里可能有很多可以优化的地方。(例如一个选项:将其作为SQLite数据库,启动时复制它,使用随机排序进行选择;另一个选项:使你的行固定宽度,并使用跳过(长度 * rand())来选择你的单词) - undefined
你的数组将占用8000 KB(每个字符2字节)+ 360000 * 几个字节用于字符串对象,可以近似计算为10MB,这太多了,无法考虑保留在内存中。另外,加载仍需要花费10-20秒的时间。 - undefined
6个回答

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

1

将其存储为CSV(逗号分隔值)。打开读取流,定位到随机位置并读取一小块字符,直到您两次遇到','。现在,从您已读取的字符串中提取逗号之间的单词。此外,您可以使用自定义分隔符,例如'#'或'|',而不是逗号。

更新:使用{{link1:RandomAccessFile}}。

更新2:如果您每行存储一个单词,即用\n分隔,则{{link2:readLine()}}会使此过程更加容易。


为什么不使用现有的分隔符(可能是'\n')?用','替换'\n'并没有真正改变任何东西,而且会使文件更难编辑。除此之外,我喜欢寻找位置N,然后向前扫描找到一个单词的起始点的想法。(这意味着长单词被选中的概率比短单词更高,但在这里可能并不重要。) - undefined

0
你可以(而且最好)创建一个单词数据库,并通过查询来获取随机单词。你可以先阅读在SQL数据库中保存数据开始。

0
文件读取是一项耗时的操作,这是正常的。你应该考虑创建一个文件组,而不是一个文件,每次启动应用程序时,可以从中随机选择一个文件和一个随机单词。这样应该能减少所需的时间。
此外,你可以将单词名称存储在XML文件中作为应用资源,放在/res/values/文件夹中。你可以创建字符串数组资源,并且可以轻松地从代码中获取它,而不必担心打开/关闭/读取文件。这非常直观。但是重要的是将所有单词分割成不同的XML文件(或至少字符串数组组)在这里。在这里阅读更多信息。http://developer.android.com/guide/topics/resources/string-resource.html#StringArray 另一种选择是通过从文件中读取将所有内容存储到数据库中。这样,只有在应用程序首次启动时才需要时间。在此过程中,你可以显示一条消息,例如“正在准备数据库...”或类似的内容。

0

这里有一些需要注意的事项:

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

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

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

  4. 现在其他人都告诉你考虑写入sqlite数据库,甚至复制一个.db文件本身(可以单独生成),选择一个随机项将会很简单。我更愿意警告你在数据库中写入36万条记录。使用默认的ContentProvider insert(Uri, ContentValues)方法可以工作,但我不会感到惊讶,如果这比解析文件本身花费的时间更长。这里的诀窍是重写ContentProvider的bulkInsert(Uri, ContentValues[])方法,使用编译好的语句。默认实现只是多次调用insert方法,因此每次插入都会打开和关闭SqliteDatabase对象。你要做的是在顶部调用beginTransaction,有一个for循环来insert每个ContentValues中的项,然后调用setTransactionSuccessful,最后调用endTransaction


0
执行代码以在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开发者指南


对不起,我回复晚了,最终我找到了一个解决方案,但我仍然有一个问题:我想要添加一个进度条来通知用户文件正在加载中,但是由于我正在反序列化一个对象,我该如何处理它呢?我的想法是启动一个线程,在序列化未准备好时递增一个计数器。 - undefined

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