使用Java在文本文件上执行二进制搜索

时间:2015-03-21 23:25:44

标签: java file binary-search

我有一个大约100万字的大文本文件。我正在为Android手机游戏做这个,我只是想看看文本文件中是否存在单词。将任何内容加载到内存中都不是一个选择。 Android手机内存和处理器非常弱,读取此文件大约需要20秒。

我修改了这个文本文件的单词,宽度相等。对于换行,每个单词为50个字符+ 1。但是,我对如何正确实现二进制搜索感到有些困惑,因为我一直对我应该为seek()正常工作添加多少字节感到困惑。

public static long search(RandomAccessFile file, String target)
            throws IOException {

    file.seek(0);
    String line = file.readLine();

    if(line.equals(target))
        return 1;

    long start = 0;
    long end = file.length();
    long mid = (start + end -50)/2;

    while(start <= end)
    {
        file.seek(mid);
        line = file.readLine();
        if(line.compareTo(target) < 0)
            start = mid + 51;
        else if(line.equalsIgnoreCase(target))
            return 1;
        else
            end = mid - 51;

        mid = (start + end)/2;
    }

    if(start > end)
        return 0;

    return -1;
}

我第一次设置结束时减去50,因为最后一个单词没有换行。经过几次迭代后,这将停止正常工作。我无法弄清楚如何正确地完成这项工作。任何人都可以指导我做错了吗?

2 个答案:

答案 0 :(得分:2)

通过将文件包装在AbstractList中,您可以利用开箱即用的二进制搜索实现:

final int size = (int) ((file.length() + LINE_BREAK_LEN) / (WORD_LEN + LINE_BREAK_LEN));
return Collections.binarySearch(
    new AbstractList<String>() {
        public String get(int pIdx) {
            try {
                file.seek((WORD_LEN + LINE_BREAK_LEN) * pIdx);
                return file.readLine();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }

        public int size() {return size;}
    },
    target,
    Comparator.comparing(String::toLowerCase)
);

请注意,换行符只会使代码复杂化,可以从文件中省略。

答案 1 :(得分:1)

Waite 的回答很好,但缺少标记接口 RandomAccess 的实现。 没有它,Collections.binarySearch 默认执行顺序 O(n) 搜索,这是您绝对不想要的。

不幸的是,Java 似乎不允许匿名类既扩展又实现(或实现多于 1 件事),因此您需要使用稍微冗长的替代方法:

  public static long search(RandomAccessFile file, String target) throws IOException {

    final int size = (int) ((file.length() + LINE_BREAK_LEN) / (WORD_LEN + LINE_BREAK_LEN));

    class FileAsList extends AbstractList<String> implements RandomAccess {
      @Override
      public String get(int pIdx) {
        try {
          file.seek((WORD_LEN + LINE_BREAK_LEN) * pIdx);
          return file.readLine();
        } catch (IOException ex) {
          throw new RuntimeException(ex);
        }
      }

      @Override
      public int size() {
        return size;
      }
    }

    var list = new FileAsList();
    return Collections.binarySearch(list, target, Comparator.comparing(String::toLowerCase));
  }