[英]Reading file takes too long
我的应用程序首先从SD卡中解析了一个约100MB的文件,然后花了几分钟时间。 为了弄清楚这一点,在我的PC上,解析同一文件需要几秒钟。
我首先使用Matcher和Pattern天真地实现了解析器,但是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实例的容量。 这可以减少增量重新分配的数量。 数组列表
其他人认为您可以尝试:
从上到下,读取字符看起来像这样:
InputStream
读取一个字节(通常取决于编码); 当您读入缓冲区时 ,会发生相同的事件序列,但是一次传递了数千个字节。
由此,您当然可以建立一个直觉,为什么一次从一个文件读取一个字符很慢。
我看不到使用Pattern
and Matcher
方法有什么问题:如果表达式编写正确,并且Patern
仅编译一次并重用,那么它应该非常快。
您怀疑String#split
也使用一个正则表达式,并在每次调用它时对其进行重新编译。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.