在 Java 中排序(内存映射 ?)文件中进行二进制搜索

我正在努力将Perl程序移植到Java,并随时学习Java。原始程序的核心组件是一个Perl模块,它使用二进制搜索在+500 GB排序的文本文件中执行字符串前缀查找(实质上,“seek”到文件中间的字节偏移量,回溯到最近的换行符,将行前缀与搜索字符串进行比较,“seek”到字节偏移量的一半/两倍, 重复直到找到...)

我已经尝试了几种数据库解决方案,但发现对于这种大小的数据集,在纯粹的查找速度方面,没有什么比这更好的了。您知道任何实现此类功能的现有 Java 库吗?如果做不到这一点,你能给我指出一些惯用的示例代码,这些代码在文本文件中进行随机访问读取吗?

或者,我不熟悉新的(?Java I/O 库,但是是否可以选择内存映射 500 GB 文本文件(我在 64 位计算机上,有备用内存)并对内存映射字节数组执行二进制搜索?我非常有兴趣听到您分享的有关此问题和类似问题的任何经验。


答案 1

我是Java的MappedByteBuffers忠实粉丝,用于这种情况。它的速度非常快。下面是我为您整理的一个片段,它将缓冲区映射到文件,查找到中间,然后向后搜索到换行符。这应该足以让你继续前进吗?

我在自己的应用程序中有类似的代码(搜索,阅读,重复直到完成),在生产环境中对流进行基准测试,并将结果发布在我的博客上(标记为“java.nio”的Geekomatic帖子)与原始数据,图形和所有内容。java.ioMappedByteBuffer

两个第二个总结?我基于MappedByteBuffer的实现速度提高了约275%。新浪网.

为了处理大于~2GB的文件,由于强制转换和,这是一个问题,我精心设计了由数组支持的分页算法。您需要在64位系统上工作才能处理大于2-4GB的文件,因为MBB使用操作系统的虚拟内存系统来发挥其魔力。.position(int pos)MappedByteBuffer

public class StusMagicLargeFileReader  {
    private static final long PAGE_SIZE = Integer.MAX_VALUE;
    private List<MappedByteBuffer> buffers = new ArrayList<MappedByteBuffer>();
    private final byte raw[] = new byte[1];

    public static void main(String[] args) throws IOException {
        File file = new File("/Users/stu/test.txt");
        FileChannel fc = (new FileInputStream(file)).getChannel(); 
        StusMagicLargeFileReader buffer = new StusMagicLargeFileReader(fc);
        long position = file.length() / 2;
        String candidate = buffer.getString(position--);
        while (position >=0 && !candidate.equals('\n')) 
            candidate = buffer.getString(position--);
        //have newline position or start of file...do other stuff    
    }
    StusMagicLargeFileReader(FileChannel channel) throws IOException {
        long start = 0, length = 0;
        for (long index = 0; start + length < channel.size(); index++) {
            if ((channel.size() / PAGE_SIZE) == index)
                length = (channel.size() - index *  PAGE_SIZE) ;
            else
                length = PAGE_SIZE;
            start = index * PAGE_SIZE;
            buffers.add(index, channel.map(READ_ONLY, start, length));
        }    
    }
    public String getString(long bytePosition) {
        int page  = (int) (bytePosition / PAGE_SIZE);
        int index = (int) (bytePosition % PAGE_SIZE);
        raw[0] = buffers.get(page).get(index);
        return new String(raw);
    }
}

答案 2

我有同样的问题。我正在尝试在排序文件中查找以某个前缀开头的所有行。

这是我编写的一种方法,它主要是在这里找到的Python代码的移植:http://www.logarithmic.net/pfh/blog/01186620415

我已经测试过了,但还没有彻底。但是,它不使用内存映射。

public static List<String> binarySearch(String filename, String string) {
    List<String> result = new ArrayList<String>();
    try {
        File file = new File(filename);
        RandomAccessFile raf = new RandomAccessFile(file, "r");

        long low = 0;
        long high = file.length();

        long p = -1;
        while (low < high) {
            long mid = (low + high) / 2;
            p = mid;
            while (p >= 0) {
                raf.seek(p);

                char c = (char) raf.readByte();
                //System.out.println(p + "\t" + c);
                if (c == '\n')
                    break;
                p--;
            }
            if (p < 0)
                raf.seek(0);
            String line = raf.readLine();
            //System.out.println("-- " + mid + " " + line);
            if (line.compareTo(string) < 0)
                low = mid + 1;
            else
                high = mid;
        }

        p = low;
        while (p >= 0) {
            raf.seek(p);
            if (((char) raf.readByte()) == '\n')
                break;
            p--;
        }

        if (p < 0)
            raf.seek(0);

        while (true) {
            String line = raf.readLine();
            if (line == null || !line.startsWith(string))
                break;
            result.add(line);
        }

        raf.close();
    } catch (IOException e) {
        System.out.println("IOException:");
        e.printStackTrace();
    }
    return result;
}

推荐