web-dev-qa-db-ja.com

バッファリングされたRandomAccessFile java

RandomAccessFileは、ファイルへのランダムアクセスには非常に時間がかかります。バッファリングされたレイヤーをその上に実装することについてよく読んでいますが、これを行うコードをオンラインで見つけることはできません。

だから私の質問は:このクラスのオープンソース実装を知っている人はポインタを共有しますか、それともあなた自身の実装を共有しますか?

この質問が、この問題に関する有用なリンクとコードのコレクションとして判明した場合、それは多くの人に共有されており、Sunによって適切に対処されていないことは間違いありません。

ファイルはInteger.MAX_VALUEよりもはるかに大きくなる可能性があるため、MemoryMappingを参照しないでください。

19
marcorossi

ええと、ファイルがInteger.MAX_VALUEより大きい場合でも、Java.nio.MappedByteBufferを使用しない理由はわかりません。

明らかに、ファイル全体に対して単一のMappedByteBufferを定義することは許可されません。ただし、ファイルのさまざまな領域にアクセスする複数のMappedByteBufferが存在する可能性があります。

FileChannenel.mapの位置とサイズの定義はlong型です。これは、Integer.MAX_VALUEを超える値を指定できることを意味します。注意する必要があるのは、サイズだけです。 bufferはInteger.MAX_VALUEより大きくなりません。

したがって、次のようにいくつかのマップを定義できます。

buffer[0] = fileChannel.map(FileChannel.MapMode.READ_WRITE,0,2147483647L);
buffer[1] = fileChannel.map(FileChannel.MapMode.READ_WRITE,2147483647L, Integer.MAX_VALUE);
buffer[2] = fileChannel.map(FileChannel.MapMode.READ_WRITE, 4294967294L, Integer.MAX_VALUE);
...

要約すると、サイズはInteger.MAX_VALUEより大きくすることはできませんが、開始位置はファイル内のどこにあってもかまいません。

本の中で Java NIO 、著者のロン・ヒッチェンズは次のように述べています。

メモリマッピングメカニズムを介してファイルにアクセスすることは、チャネルを使用している場合でも、従来の方法でデータを読み書きするよりもはるかに効率的です。明示的なシステムコールを行う必要はありません。これには時間がかかる可能性があります。さらに重要なことに、オペレーティングシステムの仮想メモリシステムは、メモリページを自動的にキャッシュします。これらのページはシステムメモリを使用してキャッシュされ、JVMのメモリヒープからスペースを消費しません。

メモリページが有効になると(ディスクから取り込まれると)、データを取得するために別のシステムコールを実行しなくても、ハードウェアのフルスピードで再度アクセスできます。頻繁に参照または更新されるインデックスまたはその他のセクションを含む大きな構造化ファイルは、メモリマッピングから多大な恩恵を受けることができます。クリティカルセクションを保護し、トランザクションの原子性を制御するためにファイルロックと組み合わせると、メモリマップされたバッファをどのように有効に活用できるかがわかり始めます。

サードパーティのAPIがそれよりも優れた機能を備えているとは思えません。おそらく、作業を簡素化するために、このアーキテクチャの上に記述されたAPIを見つけるかもしれません。

このアプローチがあなたのために働くべきだと思いませんか?

12
Edwin Dalorzo

次のようなコードを使用して、RandomAccessFileからBufferedInputStreamを作成できます。

 RandomAccessFile raf = ...
 FileInputStream fis = new FileInputStream(raf.getFD());
 BufferedInputStream bis = new BufferedInputStream(fis);

注意すべき点

  1. FileInputStreamを閉じると、RandomAccessFileが閉じ、その逆も同様です。
  2. RandomAccessFileとFileInputStreamは同じ位置を指しているため、FileInputStreamから読み取ると、RandomAccessFileのファイルポインターが進み、その逆も同様です。

おそらく、これを使用したい方法は次のようになります。

RandomAccessFile raf = ...
FileInputStream fis = new FileInputStream(raf.getFD());
BufferedInputStream bis = new BufferedInputStream(fis);

//do some reads with buffer
bis.read(...);
bis.read(...);

//seek to a a different section of the file, so discard the previous buffer
raf.seek(...);
bis = new BufferedInputStream(fis);
bis.read(...);
bis.read(...);
11
sbridges

RandomAccessFileは、ファイルへのランダムアクセスには非常に時間がかかります。バッファリングされたレイヤーをその上に実装することについてよく読んでいますが、これを行うコードをオンラインで見つけることはできません。

さて、オンラインで見つけることは可能です。
1つは、jpeg2000のJAIソースコードに実装があり、さらに邪魔にならない実装があります: http://www.unidata.ucar.edu/software/netcdf- Java /

javadocs:

http://www.unidata.ucar.edu/software/thredds/v4.3/netcdf-Java/v4.0/javadoc/ucar/unidata/io/RandomAccessFile.html

2
javatothebone

64ビットマシンで実行している場合は、メモリマップトファイルが最善のアプローチです。ファイル全体を同じサイズのバッファーの配列にマップし、必要に応じて各レコードのバッファーを選択するだけです(つまり、edalorzoの答えですが、バッファーが重複しないようにする必要があります。境界にまたがるレコード)。

32ビットJVMで実行している場合は、RandomAccessFileでスタックします。ただし、これを使用して、レコード全体を含むbyte[]を読み取り、ByteBufferを使用してその配列から個々の値を取得できます。最悪の場合、2つのファイルアクセスを行う必要があります。1つはレコードの位置/サイズを取得するため、もう1つはレコード自体を取得するためです。

ただし、byte[]を大量に作成すると、ガベージコレクターにストレスがかかり始める可能性があり、ファイル全体をバウンスするとIOバウンドのままになることに注意してください。

1
Anon
import Java.io.File;
import Java.io.FileNotFoundException;
import Java.io.IOException;
import Java.io.RandomAccessFile;

/**
 * Adds caching to a random access file.
 * 
 * Rather than directly writing down to disk or to the system which seems to be
 * what random access file/file channel do, add a small buffer and write/read from
 * it when possible. A single buffer is created, which means reads or writes near 
 * each other will have a speed up. Read/writes that are not within the cache block 
 * will not be speed up. 
 * 
 *
 */
public class BufferedRandomAccessFile implements AutoCloseable {

    private static final int DEFAULT_BUFSIZE = 4096;

    /**
     * The wrapped random access file, we will hold a cache around it.
     */
    private final RandomAccessFile raf;

    /**
     * The size of the buffer
     */
    private final int bufsize;

    /**
     * The buffer.
     */
    private final byte buf[];


    /**
     * Current position in the file.
     */
    private long pos = 0;

    /**
     * When the buffer has been read, this tells us where in the file the buffer
     * starts at.
     */
    private long bufBlockStart = Long.MAX_VALUE;


    // Must be updated on write to the file
    private long actualFileLength = -1;

    boolean changeMadeToBuffer = false;

    // Must be update as we write to the buffer.
    private long virtualFileLength = -1;

    public BufferedRandomAccessFile(File name, String mode) throws FileNotFoundException {
        this(name, mode, DEFAULT_BUFSIZE);
    }

    /**
     * 
     * @param file
     * @param mode how to open the random access file.
     * @param b size of the buffer
     * @throws FileNotFoundException
     */
    public BufferedRandomAccessFile(File file, String mode, int b) throws FileNotFoundException {
        this(new RandomAccessFile(file, mode), b);
    }

    public BufferedRandomAccessFile(RandomAccessFile raf) throws FileNotFoundException {
        this(raf, DEFAULT_BUFSIZE);
    }

    public BufferedRandomAccessFile(RandomAccessFile raf, int b) {
        this.raf = raf;
        try {
            this.actualFileLength = raf.length();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        this.virtualFileLength = actualFileLength;
        this.bufsize = b;
        this.buf = new byte[bufsize];
    }

    /**
     * Sets the position of the byte at which the next read/write should occur.
     * 
     * @param pos
     * @throws IOException
     */
    public void seek(long pos) throws IOException{
        this.pos = pos;
    }

    /**
     * Sets the length of the file.
     */
    public void setLength(long fileLength) throws IOException {
        this.raf.setLength(fileLength);
        if(fileLength < virtualFileLength) {
            virtualFileLength = fileLength;
        }
    }

    /**
     * Writes the entire buffer to disk, if needed.
     */
    private void writeBufferToDisk() throws IOException {
        if(!changeMadeToBuffer) return;
        int amountOfBufferToWrite = (int) Math.min((long) bufsize, virtualFileLength - bufBlockStart);
        if(amountOfBufferToWrite > 0) {
            raf.seek(bufBlockStart);
            raf.write(buf, 0, amountOfBufferToWrite);
            this.actualFileLength = virtualFileLength;
        }
        changeMadeToBuffer = false;
    }

    /**
     * Flush the buffer to disk and force a sync.
     */
    public void flush() throws IOException {
        writeBufferToDisk();
        this.raf.getChannel().force(false);
    }

    /**
     * Based on pos, ensures that the buffer is one that contains pos
     * 
     * After this call it will be safe to write to the buffer to update the byte at pos,
     * if this returns true reading of the byte at pos will be valid as a previous write
     * or set length has caused the file to be large enough to have a byte at pos.
     * 
     * @return true if the buffer contains any data that may be read. Data may be read so long as
     * a write or the file has been set to a length that us greater than the current position.
     */
    private boolean readyBuffer() throws IOException {
        boolean isPosOutSideOfBuffer = pos < bufBlockStart || bufBlockStart + bufsize <= pos;

        if (isPosOutSideOfBuffer) {

            writeBufferToDisk();

            // The buffer is always positioned to start at a multiple of a bufsize offset.
            // e.g. for a buf size of 4 the starting positions of buffers can be at 0, 4, 8, 12..
            // Work out where the buffer block should start for the given position. 
            long bufferBlockStart = (pos / bufsize) * bufsize;

            assert bufferBlockStart >= 0;

            // If the file is large enough, read it into the buffer.
            // if the file is not large enough we have nothing to read into the buffer,
            // In both cases the buffer will be ready to have writes made to it.
            if(bufferBlockStart < actualFileLength) {
                raf.seek(bufferBlockStart);
                raf.read(buf);
            }

            bufBlockStart = bufferBlockStart;
        }

        return pos < virtualFileLength;
    }

    /**
     * Reads a byte from the file, returning an integer of 0-255, or -1 if it has reached the end of the file.
     * 
     * @return
     * @throws IOException 
     */
    public int read() throws IOException {
        if(readyBuffer() == false) {
            return -1;
        }
        try {
            return (buf[(int)(pos - bufBlockStart)]) & 0x000000ff ; 
        } finally {
            pos++;
        }
    }

    /**
     * Write a single byte to the file.
     * 
     * @param b
     * @throws IOException
     */
    public void write(byte b) throws IOException {
        readyBuffer(); // ignore result we don't care.
        buf[(int)(pos - bufBlockStart)] = b;
        changeMadeToBuffer = true;
        pos++;
        if(pos > virtualFileLength) {
            virtualFileLength = pos;
        }
    }

    /**
     * Write all given bytes to the random access file at the current possition.
     * 
     */
    public void write(byte[] bytes) throws IOException {
        int writen = 0;
        int bytesToWrite = bytes.length;
        {
            readyBuffer();
            int startPositionInBuffer = (int)(pos - bufBlockStart);
            int lengthToWriteToBuffer = Math.min(bytesToWrite - writen, bufsize - startPositionInBuffer);
            assert  startPositionInBuffer + lengthToWriteToBuffer <= bufsize;

            System.arraycopy(bytes, writen,
                            buf, startPositionInBuffer,
                            lengthToWriteToBuffer);
            pos += lengthToWriteToBuffer;
            if(pos > virtualFileLength) {
                virtualFileLength = pos;
            }
            writen += lengthToWriteToBuffer;
            this.changeMadeToBuffer = true;
        }

        // Just write the rest to the random access file
        if(writen < bytesToWrite) {
            writeBufferToDisk();
            int toWrite = bytesToWrite - writen;
            raf.write(bytes, writen, toWrite);
            pos += toWrite;
            if(pos > virtualFileLength) {
                virtualFileLength = pos;
                actualFileLength = virtualFileLength;
            }
        }
    }

    /**
     * Read up to to the size of bytes,
     * 
     * @return the number of bytes read.
     */
    public int read(byte[] bytes) throws IOException {
        int read = 0;
        int bytesToRead = bytes.length;
        while(read < bytesToRead) {

            //First see if we need to fill the cache
            if(readyBuffer() == false) {
                //No more to read;
                return read;
            }

            //Now read as much as we can (or need from cache and place it
            //in the given byte[]
            int startPositionInBuffer = (int)(pos - bufBlockStart);
            int lengthToReadFromBuffer = Math.min(bytesToRead - read, bufsize - startPositionInBuffer);

            System.arraycopy(buf, startPositionInBuffer, bytes, read, lengthToReadFromBuffer);

            pos += lengthToReadFromBuffer;
            read += lengthToReadFromBuffer;
        }

        return read;
    }

    public void close() throws IOException {
        try {
            this.writeBufferToDisk();
        } finally {
            raf.close();
        }
    }

    /**
     * Gets the length of the file.
     * 
     * @return
     * @throws IOException
     */
    public long length() throws IOException{
        return virtualFileLength;
    }

}
0
Luke