我的应用程序首先从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 上。我想只读一个字谜会花很多钱。
现在我有点灵感了。有提示吗?
答案 0 :(得分:2)
可能提高效果的另一个选择是在InputStreamReader
周围使用FileInputStream
。你必须自己做缓冲,但这绝对可以提高性能。有关详细信息,请参阅this tutorial - 但请勿盲目跟踪。例如,当您使用char数组时,可以使用char数组作为缓冲区(当您到达换行符时将其发送到treatLine()
)。
另一个建议是直接使用Thread
。 AsyncTask
上的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
其他人认为你可以尝试:
答案 5 :(得分:0)
从上到下,阅读角色如下:
InputStream
读取一个字节(通常取决于编码); 当您读入缓冲区时,会发生相同的事件序列,但一次传输会传输数千个字节。
从这里你可以建立一种直觉,为什么从文件中一次读取一个字符非常慢。
我看不出Pattern
和Matcher
方法有什么问题:如果表达式写得正确,Patern
只编译一次并重复使用,它应该非常快。
String#split
也使用正则表达式,并在每次调用时重新编译它。