繁体   English   中英

读取文件时间过长

[英]Reading file takes too long

我的应用程序首先从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上 我想只读一个字符会花很多钱。

现在我有点灵感了。 有小费吗?

这可能会提高性能的另一种选择是使用InputStreamReader周围FileInputStream 您必须自己进行缓冲,但这绝对可以提高性能。 有关更多信息,请参见本教程 -但是不要盲目地跟随它。 例如,当您使用char数组时,可以将char数组用作缓冲区(并在到达treatLine()时将其发送到treatLine() )。

另一个建议是直接使用Thread 关于AsyncTask 文档说(我的语气):

AsyncTask被设计为围绕Thread和Handler的帮助器类,并且不构成通用的线程框架。 理想情况下,应将AsyncTasks用于较短的操作(最多几秒钟)。如果需要使线程长时间运行,则强烈建议您使用java.util.concurrent pacakge提供的各种API,例如执行程序,ThreadPoolExecutor和FutureTask。

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

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

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

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

尝试一次获取大量数据。

删除BufferedReader会使情况变得更糟。 当然。 确实需要“填满中间缓冲区”。 使用FileReader目录每个字符时,它可以节省8191个系统调用中的8191个。 缓冲的I / O总是更快。 我不知道你为什么会想到别的。

如@EJP所述,您应该使用BufferedReader。 但从根本上讲,您正在移动设备上运行,而不是PC。 闪存的读取速度远远不能与PC相比,其计算能力仅是运行于3.5 GHz的4核8线程i7的一小部分,我们甚至都没有考虑过以全速运行闪存和CPU的情况。会影响设备的电池寿命。

因此,您应该问自己的真正问题是,为什么您的应用程序需要解析100 MB的数据? 而且,如果每次启动时都需要对其进行解析,那么为什么不可以仅在PC上对其进行解析,而不必让用户这样做呢?

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

每个ArrayList实例都有一个容量。 容量是用于在列表中存储元素的数组的大小。 它总是至少与列表大小一样大。 将元素添加到ArrayList后,其容量会自动增长。 除了添加元素具有固定的摊销时间成本外,没有指定增长策略的详细信息。

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

其他人认为您可以尝试:

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

关于文件读取

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

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

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

由此,您当然可以建立一个直觉,为什么一次从一个文件读取一个字符很慢。

关于正则表达式

我看不到使用Pattern and Matcher方法有什么问题:如果表达式编写正确,并且Patern仅编译一次并重用,那么它应该非常快。

您怀疑String#split也使用一个正则表达式,并在每次调用它时对其进行重新编译。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM