我想读取一个150MB的文本文件,并将该文件的内容拆分为单词。当我使用MappedByteBuffer做这件事时,135MB的文件大小需要12秒。当我用BufferedReader做同样的事情时,会花费更多的时间。有没有可能缩短时间?
这是我的代码。
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.ConcurrentHashMap;
public class mappedcompare {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
long one =System.currentTimeMillis();
String line=null;
File f= new File("D:\\dinesh\\janani.txt");
FileInputStream fin = new FileInputStream(f);
FileChannel fc = fin.getChannel();
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0L, fc.size());
String[] words=null;
ConcurrentHashMap <String,Integer> dictionary=new ConcurrentHashMap<String,Integer>(50,1);
byte[] buffer = new byte[(int) fc.size()];
mbb.get(buffer);
ByteArrayInputStream isr = new ByteArrayInputStream(buffer);
InputStreamReader ip = new InputStreamReader(isr);
BufferedReader br = new BufferedReader(ip);
while((line=br.readLine())!=null){
line=line.replace(':', ' ');
line=line.replace(';', ' ');
line=line.replace('"', ' ');
line=line.replace('!', ' ');
line=line.replace(',',' ');
line=line.replace('.', ' ');
line =line.replace('/', ' ');
line=line.replace('\\', ' ');
line=line.replace('%', ' ');
line=line.replace('(', ' ');
line=line.replace(')', ' ');
line=line.replace('\'', ' ');
for(String word: line.split("\\s+"))
{
dictionary.putIfAbsent(word, 1);
if(dictionary.containsKey("word")){
int value =dictionary.get(word);
dictionary.replace(word, ++value);
}
}
}
System.out.println(System.currentTimeMillis() - one);
fin.close();
}
}发布于 2014-06-24 23:24:08
首先,不要在单线程操作中使用ConcurrentHashMap。与简单的HashMap相比,使用这个类没有任何好处。在Java7中,HashMap没有提供putIfAbsent等操作,但这不是一个限制,而是一个清理Map更新代码的机会:
dictionary.putIfAbsent(word, 1);
if(dictionary.containsKey("word")){
int value =dictionary.get(word);
dictionary.replace(word, ++value);
}在这里,您正在执行四个散列查找操作,putIfAbsent、containsKey、get和replace,而您实际上只需要两个操作(除此之外,在我看来,查找"word"而不是word似乎失败了):
Integer old=dictionary.get(word);
dictionary.put(word, old==null? 1: old+1);这只需要两次查找,并且适用于普通的HashMap。
接下来,去掉line=line.replace(…, ' ');调用序列,因为每个调用都会创建一个新的String,其中您真正想要的是在split操作中处理这些特殊字符,如' '。因此,您只需调整您的split操作,以将这些字符作为分隔符:for(String word: line.split("[:;\"!,./\\\\%()'\\s]+"))。
因此,把所有这些放在一起,你的代码变得更具可读性,这比你能节省的几秒钟更大的胜利。
File f= new File("D:\\dinesh\\janani.txt");
try(FileInputStream fin = new FileInputStream(f);
FileChannel fc = fin.getChannel();) {
final MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0L, fc.size());
HashMap<String, Integer> dictionary=new HashMap<>();
byte[] buffer = new byte[(int) fc.size()];
mbb.get(buffer);
ByteArrayInputStream isr = new ByteArrayInputStream(buffer);
InputStreamReader ip = new InputStreamReader(isr);
BufferedReader br = new BufferedReader(ip);
while((line=br.readLine())!=null){
for(String word: line.split("[:;\"!,./\\\\%()'\\s]+")) {
Integer old=dictionary.get(word);
dictionary.put(word, old==null? 1: old+1);
}
}
}最后,我建议您尝试一下Files.readAllLines(…)。这将取决于环境,它是否更快,但即使它稍微慢一点,由于可读性的优势,我更喜欢它而不是你的MappedByteBuffer方法:
File f= new File("D:\\dinesh\\janani.txt");
HashMap<String, Integer> dictionary=new HashMap<>();
for(String line:Files.readAllLines(f.toPath(), Charset.defaultCharset())) {
for(String word: line.split("[:;\"!,./\\\\%()'\\s]+")) {
Integer old=dictionary.get(word);
dictionary.put(word, old==null? 1: old+1);
}
}如果性能真的那么重要,您可以深入一层,在byte级别上手动处理拆分,并仅在找到匹配项时创建String。这假设您使用的编码为每个char使用一个byte,并直接映射较低的值(即ASCII字符),这是像窗口CP1258这样的常见编码的情况。
HashMap<String, Integer> dictionary=new HashMap<>();
final CharsetDecoder cs = Charset.defaultCharset().newDecoder();
assert cs.averageCharsPerByte()==1;
try(FileChannel ch=FileChannel.open(f.toPath(), StandardOpenOption.READ)) {
MappedByteBuffer mbb=ch.map(MapMode.READ_ONLY, 0, ch.size());
ByteBuffer slice=mbb.asReadOnlyBuffer();
int start=0;
while(mbb.hasRemaining()) {
switch(mbb.get()) {
case ' ': case 9: case 10: case 11: case 13: case '\f':
case ':': case ';': case '\\': case '"': case '!': case ',':
case '.': case '/': case '%': case '(': case ')': case '\'':
int pos=mbb.position();
if(pos>start) {
slice.limit(mbb.position()).position(start);
String word=cs.decode(slice).toString();
Integer old=dictionary.get(word);
dictionary.put(word, old==null? 1: old+1);
start=mbb.position();
}
start=pos+1;
}
}
}这可以极大地加速这种低级操作,但代价是不能完全移植。
发布于 2014-06-25 00:12:15
我尽量减少手术的次数。对于我创建的示例文件,这最终比原始代码快了大约3倍。这可能不适用于大多数更复杂的字符编码(请参阅Holger的替代方法answer,它应该适用于任何字符编码)。
long one = System.currentTimeMillis();
boolean[] isDelimiter = new boolean[127];
isDelimiter[' '] = true;
isDelimiter['\t'] = true;
isDelimiter[':'] = true;
isDelimiter[';'] = true;
isDelimiter['"'] = true;
isDelimiter['!'] = true;
isDelimiter[','] = true;
isDelimiter['.'] = true;
isDelimiter['/'] = true;
isDelimiter['\\'] = true;
isDelimiter['%'] = true;
isDelimiter['('] = true;
isDelimiter[')'] = true;
isDelimiter['\''] = true;
isDelimiter['\r'] = true;
isDelimiter['\n'] = true;
class Counter {
int count = 0;
}
File f = // your file here
FileInputStream fin = new FileInputStream(f);
FileChannel fc = fin.getChannel();
MappedByteBuffer mbb = fc
.map(FileChannel.MapMode.READ_ONLY, 0L, f.length());
Map<String, Counter> dictionary = new HashMap<String, Counter>();
StringBuilder wordBuilder = new StringBuilder();
while (mbb.hasRemaining()) {
char c = (char) mbb.get();
if (c < isDelimiter.length && c >= 0 && isDelimiter[c]) {
if (wordBuilder.length() > 0) {
String word = wordBuilder.toString();
wordBuilder.setLength(0);
Counter intForWord = dictionary.get(word);
if (intForWord == null) {
intForWord = new Counter();
dictionary.put(word, intForWord);
}
intForWord.count++;
}
} else {
wordBuilder.append(c);
}
}
System.out.println(System.currentTimeMillis() - one);
fin.close();发布于 2014-06-24 22:53:51
尝试将所有这些replace和split替换为
line.split("[:;\"!,./\\\\%()'\\s]+")您还可以尝试在流式传输时使用Java的Scanner来解析文件。您可以将上面的正则表达式传递给useDelimiter,这样它就可以拆分所有这些字符。
https://stackoverflow.com/questions/24389112
复制相似问题