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

FileMappedInputStream: 2GB以上のファイルに対応

Java utility

MappedByteBuffer + InputStream: ファイルにマッピングされたランダムアクセス可能な入力ストリーム - sileの日記で作成したクラスの巨大ファイル対応版。
2GB以上のファイルでも扱うことが可能。

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

public class FileMappedInputStream extends InputStream {
    private final ByteBuffer[] maps;
    private int cur = 0;
    private int end_map;
   
    public FileMappedInputStream(String filepath) throws IOException {
        final FileChannel cnl = new FileInputStream(filepath).getChannel();
        try {
            final int map_num = (int)(cnl.size()/Integer.MAX_VALUE)+1;
            maps = new ByteBuffer[map_num];
           
            for(int i=0; i < map_num; i++) {
                final int offset = Integer.MAX_VALUE*i;
                final int length = (int)Math.min(Integer.MAX_VALUE, cnl.size()-offset);
                maps[i] = cnl.map(FileChannel.MapMode.READ_ONLY, offset, length);
            }

            end_map = maps.length;
        } finally {
            cnl.close();
        }
    }

    private FileMappedInputStream(FileMappedInputStream src) {
        cur = src.cur;
        end_map = src.end_map;
        
        maps = new ByteBuffer[src.maps.length];
        for(int i=0; i < maps.length; i++)
            // ByteBuffer複製: 
            // - メモリ領域は共有されたまま
            // - 位置情報(position,limit)等は複製先と複製元で別に保持される
            maps[i] = src.maps[i].duplicate();
    }

    private boolean eos() {
        if(end_map == cur)
            return true;
        if(maps[cur].hasRemaining()==false) {
            cur++;
            return eos();
        }
        return false;
    }
   
    public InputStream range(long start, int length) throws IOException {
        final int map_n   = (int)(start/Integer.MAX_VALUE);
        final long offset = start%Integer.MAX_VALUE;
        final long limit  = offset+length;
       
        cur = map_n;
        maps[cur].clear().position((int)offset);
        if(limit < Integer.MAX_VALUE) {
            maps[cur].limit((int)limit);
            end_map = cur+1;
        } else {
            maps[cur].limit(maps[cur].capacity());
            maps[cur+1].clear().limit((int)(limit-Integer.MAX_VALUE));
            end_map = cur+2;
        }
        return this;
    }

    // 複数の範囲から平行して入力を行ないたい場合用のメソッド。
    //
    // 第三引数(copy)にtrueを渡した場合に返されるインスタンスは、
    // 読み込み位置情報などを他と共有することがないので、安全。
    public InputStream range(long start, int length, boolean copy) throws IOException {
        if(copy)
            return new FileMappedInputStream(this).range(start, length);
        else 
            return range(start, length);
    }

    @Override public int read() throws IOException {
        if(eos())
            return -1;
        return (int)(maps[cur].get())&0xFF;
    }

    @Override public int read(byte[] bytes, int offset, int length) throws IOException {
        if(eos())
            return -1;

        if(length < maps[cur].remaining()) {
            maps[cur].get(bytes, offset, length);
            return length;
        } else {
            final int len = maps[cur].remaining();
            maps[cur].get(bytes, offset, len);
            if(eos())
                return len;
            return len + read(bytes, offset+len, length-len);
        }
    }

    @Override public int read(byte[] bytes) throws IOException {
        return read(bytes, 0, bytes.length);
    }
}