阅读文件需要太长时间

时间:2013-07-31 23:57:25

标签: java android

我的应用程序首先从SD卡解析一个~100MB的文件,并且需要几分钟才能完成。换句话说,在我的电脑上,解析同一个文件需要几秒钟。

我开始使用匹配器模式天真地实现解析器,但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 上。我想只读一个字谜会花很多钱。

现在我有点灵感了。有提示吗?

6 个答案:

答案 0 :(得分:2)

可能提高效果的另一个选择是在InputStreamReader周围使用FileInputStream。你必须自己做缓冲,但这绝对可以提高性能。有关详细信息,请参阅this tutorial - 但请勿盲目跟踪。例如,当您使用char数组时,可以使用char数组作为缓冲区(当您到达换行符时将其发送到treatLine())。

另一个建议是直接使用ThreadAsyncTask上的Documentation说(我的语调):

  

AsyncTask旨在成为Thread和Handler的辅助类   并不构成通用的线程框架。的 AsyncTasks   理想情况下应该用于短期操作(几秒钟的时间)   大多数。) 如果你需要保持线程长时间运行,   强烈建议您使用提供的各种API   java.util.concurrent pacakge如Executor,ThreadPoolExecutor和   FutureTask。

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

答案 1 :(得分:1)

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

如果readline()很耗时,请尝试以下方法:

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

尝试一次获取大量数据。

答案 2 :(得分:1)

删除BufferedReader会让情况变得更糟。当然。你需要'填写中间缓冲区'。它通过FileReader目录为每个字符的8192系统调用节省了8191个。缓冲I / O总是更快。我不知道你为什么会这么想。

答案 3 :(得分:1)

正如@EJP所提到的,你应该使用BufferedReader。但更基本的是,你在移动设备上运行,它不是PC。闪存读取速度远不及PC,计算能力只是运行在3.5 GHz的4核8线程i7的一小部分,我们甚至没有考虑闪存和闪存的运行速度。全速CPU会影响设备的电池寿命。

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

答案 4 :(得分:0)

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

  

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

     

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

其他人认为你可以尝试:

  • 使用NDK。
  • 正如@Anson Yao所说,试着增加缓冲区的大小
  • 删除treatLine函数,以减少调用函数的开销

答案 5 :(得分:0)

关于文件阅读

从上到下,阅读角色如下:

  1. 在Java中你要求阅读一个角色;
  2. 它转换为从InputStream读取一个字节(通常取决于编码);
  3. 这适用于本机代码,它被转换为类似的操作系统命令,以从打开的文件中读取一个字节;
  4. 然后这一个字节以同样的方式传播。
  5. 当您读入缓冲区时,会发生相同的事件序列,但一次传输会传输数千个字节。

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

    关于正则表达式

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

    您怀疑

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