読者です 読者をやめる 読者になる 読者になる

MappedByteBuffer + InputStream: ファイルにマッピングされたランダムアクセス可能な入力ストリーム

Java

表題の通り、ファイルにマッピングされておりかつランダムアクセス可能な入力ストリームクラス、のサンプル。
MappedByteBufferを読み込み専用モード(FileChannel.MapMode.READ_ONLY)で使っているので、同じファイルを複数プロセスでオープンした場合はメモリ領域が共有されるので、省メモリ(おそらく少なくともLinux系では…)
一つの大きな辞書ファイルに対して、多数のプロセスが参照のみを行う場合等に有用だと考え作成。

import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

// サンプルmainメソッド用のimport文
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class FileMappedInputStream extends InputStream {
    private final ByteBuffer map;
    
    public FileMappedInputStream(String filepath) throws IOException {
        FileChannel cnl = new FileInputStream(filepath).getChannel();
        try {
            // ファイル全体をマッピング
            // ※ マッピング可能なファイルの最大サイズは2GBまで(おそらく。未検証)
            map = cnl.map(FileChannel.MapMode.READ_ONLY, 0, cnl.size());

            // 今回は不要だが、マルチバイトのデータを読み込む場合は、次のようにしてバイトオーダーを設定可能
            // map.order(java.nio.ByteOrder.xxx);
        } finally {
            cnl.close();
        }
    }

    // ランダムアクセス用のメソッド
    // 入力ストリームが読み込む範囲(位置)を変更する
    public InputStream range(int start, int end) throws IOException {
        map.position(start).limit(end);
        return this;
    }

    // InputStream.readメソッドの1
    @Override public int read() throws IOException {
        if(map.hasRemaining()==false)
            return -1;
        return (int)(map.get())&0xFF;
    }

    // InputStream.readメソッドの2
    @Override public int read(byte[] bytes, int offset, int length) throws IOException {
        if(map.hasRemaining()==false)
            return -1;
        final int size = Math.min(length,map.remaining());
        map.get(bytes, offset, size);
        return size;
    }

    // InputStream.readメソッドの3
    @Override public int read(byte[] bytes) throws IOException {
        return read(bytes, 0, bytes.length);
    }

    // サンプルのmainメソッド
    // 引数で指定されたファイルの中身を読み込み、そのまま標準出力に出力する
    public static void main(String[] args) throws IOException, Exception {
        if(args.length != 1) {
            System.err.println("Usage: java FileMappedInputStream <input_file>");
            System.exit(1);
        }
          
        FileMappedInputStream fmis = new FileMappedInputStream(args[0]);
        BufferedReader in = new BufferedReader(new InputStreamReader(fmis));

        // 次のようにして入力範囲を設定可能
        // fmis.range(start, end); 

        for(String s=in.readLine(); s!=null; s=in.readLine())
            System.out.println(s);
    }
}