0

我的应用程序首先从 SD 卡解析约 100MB 的文件,并且需要几分钟才能完成。从这个角度来看,在我的 PC 上,解析同一个文件需要几秒钟的时间。

我开始天真地使用MatcherPattern实现解析器,但 DDMS 告诉我,90% 的时间都花在了计算正则表达式上。而且解析文件用了半个多小时。该模式非常简单,一行包括:

ID (a number) <TAB> LANG (a 3-to-5 character string) <TAB> DATA (the rest)

我决定尝试使用String.split。它没有显示出显着的改进,可能是因为这个函数本身可能使用正则表达式。那时我决定完全重写解析器,结果是这样的:

protected Collection<Sentence> doInBackground( Void... params ) {
    BufferedReader reader = new BufferedReader( new FileReader( sentenceFile ) );

    String currentLine = null;
    while ( (currentLine = reader.readLine()) != null ) {
        treatLine( currentLine, allSentences );
    }

    reader.close();
    return allSentences;
}

private void treatLine( String line, Collection<Sentence> allSentences ) {
    char[] str = line.toCharArray();

    // ...
    // treat the array of chars into an id, a language and some data

    allSentences.add( new Sentence( id, lang, data ) );
}

我注意到一个巨大的提升。花了几分钟而不是半小时。但我对此并不满意,所以我分析并意识到瓶颈是BufferedReader.readLine。我想知道:它可能是受 IO 限制的,但也可能是需要花费大量时间来填充我并不真正需要的中间缓冲区。所以我直接使用 FileReader 重写了整个事情:

protected Collection<Sentence> doInBackground( Void... params ) {
    FileReader reader = new FileReader( sentenceFile );
    int currentChar;
    while ( (currentChar = reader.read()) != -1 ) {
        // parse an id
        // ...            

        // parse a language
        while ( (currentChar = reader.read()) != -1 ) {
            // do some parsing stuff
        }

        // parse the sentence data
        while ( (currentChar = reader.read()) != -1 ) {
            // parse parse parse
        }

        allSentences.add( new Sentence( id, lang, data ) );
    }

    reader.close();
}

我很惊讶地意识到性能非常糟糕。显然,大部分时间都花在了FileReader.read上。我想只阅读一个字符会花费很多。

现在我有点没有灵感了。任何提示?

4

6 回答 6

2

另一个可能提高性能的选项是InputStreamReader使用FileInputStream. 您必须自己进行缓冲,但这肯定会提高性能。有关更多信息,请参阅本教程- 但不要盲目地遵循它。例如,当您使用 char 数组时,您可以使用 char 数组作为缓冲区(并treatLine()在到达换行符时将其发送到)。

还有一个建议是Thread直接使用。文档AsyncTask (我的语调):

AsyncTask 被设计为围绕 Thread 和 Handler 的辅助类,并不构成通用线程框架。AsyncTasks 最好用于短时间的操作(最多几秒钟)。如果您需要保持线程长时间运行,强烈建议您使用 java.util.concurrent 包提供的各种 API,例如Executor、ThreadPoolExecutor 和 FutureTask。

此外,获得更快的 SD 卡肯定会有所帮助 - 这可能是它比台式机慢得多的主要原因。普通的 HD 可以读取 60 MB/s,慢速 SD 卡可以读取 2 MB/s。

于 2013-08-01T00:35:27.210 回答
1

删除 BufferedReader 会使情况变得更糟。当然。您确实需要“填充中间缓冲区”。它为您节省了 8191 个系统调用中的 8191 个系统调用,这些调用是您对FileReader目录的每个字符执行的。缓冲 I/O 总是更快。我不知道你为什么会有其他想法。

于 2013-08-01T00:52:24.783 回答
1

正如@EJP 所提到的,您应该使用 BufferedReader。但更根本的是,您是在移动设备上运行,而不是 PC。Flash读取速度远不及PC,计算能力是运行在3.5 GHz的4核8线程i7的一小部分,我们甚至没有考虑什么会同时运行flash和CPU全速做设备的电池寿命。

所以你应该问自己的真正问题是,为什么你的应用需要解析 100 MB 的数据?如果每次启动时都需要解析它,为什么你不能在 PC 上解析它,这样你的用户就不必解析了?

于 2013-08-01T01:26:00.183 回答
1

我想您需要保留 BufferedReader 但可能不使用 readline。FileReader 从 SD 卡读取内容,这是最慢的。BufferredReader 从内存中读取,这样比较好。您的第二种方法会增加您访问 Filereader.read() 的时间,我想这不起作用。

如果 readline() 很耗时,请尝试以下操作:

   reader.read(char[] cbuf, int off, int len) 

尝试一次获取大量数据。

于 2013-08-01T00:44:00.163 回答
0

关于文件读取

从上到下,读取一个字符看起来像这样:

  1. 在 Java 中,您请求读取一个字符;
  2. InputStream它转换为从;读取一个字节(通常取决于编码)
  3. 这进入本机代码,在那里它被转换为类似的操作系统命令以从打开的文件中读取一个字节;
  4. 然后这个字节以同样的方式返回。

当您读入一个缓冲区时,会发生相同的事件序列,但一次传输会传输数千个字节。

从这里你当然可以建立一个直觉,为什么从文件中一次读取一个字符非常慢。

关于正则表达式

我看不出PatternandMatcher方法有什么问题:如果表达式编写正确,并且Patern只编译一次并重用,它应该非常快。

String#split,正如您所怀疑的,它也使用正则表达式,并在每次调用时重新编译它。

于 2013-08-01T08:52:36.593 回答
0

allSentences 是一个 ArrayList 吗?如果是这样,可能其中的项目数量很多,并且必须多次调整大小。尝试用大容量初始化阵列。

每个 ArrayList 实例都有一个容量。容量是用于存储列表中元素的数组的大小。它总是至少与列表大小一样大。随着元素被添加到 ArrayList,它的容量会自动增长。除了添加一个元素具有恒定的摊销时间成本这一事实之外,没有指定增长策略的细节。

应用程序可以在使用 ensureCapacity 操作添加大量元素之前增加 ArrayList 实例的容量。这可以减少增量重新分配的数量。数组列表

其他认为您可以尝试:

  • 使用 NDK。
  • 正如@Anson Yao 所说,尝试增加缓冲区的大小
  • 删除treatLine函数,以减少调用函数的开销
于 2013-08-01T01:35:03.730 回答