首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >MappedByteBuffer查询

MappedByteBuffer查询
EN

Stack Overflow用户
提问于 2014-06-24 22:21:54
回答 3查看 623关注 0票数 1

我想读取一个150MB的文本文件,并将该文件的内容拆分为单词。当我使用MappedByteBuffer做这件事时,135MB的文件大小需要12秒。当我用BufferedReader做同样的事情时,会花费更多的时间。有没有可能缩短时间?

这是我的代码。

代码语言:javascript
复制
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();

    }

}
EN

回答 3

Stack Overflow用户

发布于 2014-06-24 23:24:08

首先,不要在单线程操作中使用ConcurrentHashMap。与简单的HashMap相比,使用这个类没有任何好处。在Java7中,HashMap没有提供putIfAbsent等操作,但这不是一个限制,而是一个清理Map更新代码的机会:

代码语言:javascript
复制
dictionary.putIfAbsent(word, 1);

if(dictionary.containsKey("word")){
        int value =dictionary.get(word);
        dictionary.replace(word, ++value);  
    }

在这里,您正在执行四个散列查找操作,putIfAbsentcontainsKeygetreplace,而您实际上只需要两个操作(除此之外,在我看来,查找"word"而不是word似乎失败了):

代码语言:javascript
复制
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]+"))

因此,把所有这些放在一起,你的代码变得更具可读性,这比你能节省的几秒钟更大的胜利。

代码语言:javascript
复制
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方法:

代码语言:javascript
复制
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这样的常见编码的情况。

代码语言:javascript
复制
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;
    }
  }
}

这可以极大地加速这种低级操作,但代价是不能完全移植。

票数 2
EN

Stack Overflow用户

发布于 2014-06-25 00:12:15

我尽量减少手术的次数。对于我创建的示例文件,这最终比原始代码快了大约3倍。这可能不适用于大多数更复杂的字符编码(请参阅Holger的替代方法answer,它应该适用于任何字符编码)。

代码语言:javascript
复制
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();
票数 2
EN

Stack Overflow用户

发布于 2014-06-24 22:53:51

尝试将所有这些replacesplit替换为

代码语言:javascript
复制
line.split("[:;\"!,./\\\\%()'\\s]+")

您还可以尝试在流式传输时使用Java的Scanner来解析文件。您可以将上面的正则表达式传递给useDelimiter,这样它就可以拆分所有这些字符。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24389112

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档