【问题标题】:Buffered RandomAccessFile java缓冲的 RandomAccessFile java
【发布时间】:2011-04-10 19:38:15
【问题描述】:

RandomAccessFile 对于随机访问文件非常慢。您经常阅读有关在其上实现缓冲层的信息,但在网上找不到这样做的代码。

所以我的问题是:知道这个类的任何开源实现的你们会分享一个指针还是分享你自己的实现?

如果这个问题能成为关于这个问题的有用链接和代码的集合,那就太好了,我敢肯定,许多人都共享这些问题,而 SUN 从未正确解决过这些问题。

请不要引用 MemoryMapping,因为文件可能比 Integer.MAX_VALUE 大。

【问题讨论】:

  • 让我看看我是否理解,你的意思是 java.nio.MemoryByteBuffer 不够好,因为它只能容纳 Integer.MAX_VALUE 个字节。是这样吗?
  • 缓冲区中大约有 2 吉字节的内存。您的文件有多大,有多少可用内存?
  • 你想缓冲什么/如何缓冲?通常你正在缓冲一个流,但是如果你想访问一个多演出文件中的任意点,你到底想存储什么数据?我的猜测是,答案会给你你的解决方案(例如“我总是想在随机点之后预加载 1K 的数据)。
  • @edalorzo:是的,这就是问题所在。我的文件有几十个 GIG。
  • @Will:是的,这是最典型的想法。一种预读行为。我有由标头和一些有效负载组成的记录。因此,我可以读取构成我的标头的字段的整数、长整数和短整数,其中一些字段包含接下来出现的有效负载块的大小。所以它有很多 read*() 和一些 read(byte[])s。它主要是标题+有效负载场景。我想到的实现类型与添加 BufferInputStream 类型的行为没有什么不同。

标签: java file-io io buffering random-access


【解决方案1】:

您可以使用如下代码从 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(...);

【讨论】:

  • 我采用了类似的方法,使用getFD 方法。但我没有构建一个 BufferedInputStream,而是构建了一个 FileReader,然后构建了一个 BufferedReader。这使我可以访问readLine 方法,该方法比 RandomAccessFile 提供的方法更快(并且可能对 UTF 更友好?)。
  • @JeffTerrellPh.D.我尝试了 BufferedReader 并注意到 RandomAccessFile.getFilePointer 方法即使在多次调用 BufferedReader.readLine() 方法后也会返回相同的位置。这可能是因为 BufferedReader 可能会在一次调用 readLine() 时在内部将文件指针向前推进。
【解决方案2】:

好吧,即使文件比 Integer.MAX_VALUE 大,我也没有理由不使用 java.nio.MappedByteBuffer。

显然,您将不能为整个文件定义单个 MappedByteBuffer。但是您可以让多个 MappedByteBuffers 访问文件的不同区域。

FileChannenel.map 中位置和大小的定义是 long 类型,这意味着您可以提供超过 Integer.MAX_VALUE 的值,您唯一需要注意的是 缓冲区的大小 不会大于 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》一书中,作者 Ron Hitchens 指出:

通过 内存映射机制可以很远 比阅读或写作更有效率 传统方式的数据,即使当 使用渠道。没有明确的系统 需要拨打电话,这可以是 耗时的。更重要的是, 操作系统的虚拟内存系统 系统自动缓存内存 页。这些页面将被缓存 使用系统内存并且不会 消耗 JVM 内存中的空间 堆。

一旦内存页有效 (从磁盘引入),它可以是 再次以全硬件速度访问 无需制作另一个 系统调用来获取数据。大的, 包含索引的结构化文件 或引用的其他部分 或经常更新可以受益 极大地来自内存映射。什么时候 结合文件锁定保护 临界区和控制 事务原子性,你开始 看看内存映射缓冲区如何 好好利用。

我真的怀疑你会发现第三方 API 做得比这更好。也许您会发现在此架构之上编写的 API 可以简化工作。

您不认为这种方法应该适合您吗?

【讨论】:

【解决方案3】:

RandomAccessFile 对于随机访问文件非常慢。您经常阅读有关在其上实现缓冲层的信息,但在网上找不到这样做的代码。

嗯,网上可以找到。
一方面,jpeg2000 中的 JAI 源代码有一个实现,以及一个更加不受阻碍的 impl,位于: 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

【讨论】:

  • 如果您的文件在 GB 范围内,您肯定会注意到内存映射文件的加速。我提到的缓冲 RandomAccessFile impl 非常适合小文件,而且内存要求也很低。内存映射文件占用大量 RAM 来执行它们的魔法。
  • 唯一的问题是我必须依赖整个库来上课。那就是问题所在。仍然,感谢您的链接。
【解决方案4】:

如果您在 64 位机器上运行,那么内存映射文件是您的最佳方法。只需将整个文件映射到一个大小相等的缓冲区数组中,然后根据需要为每条记录选择一个缓冲区(即,edalorzo 的答案,但是你想要重叠的缓冲区,这样你就没有跨越边界的记录)。

如果您在 32 位 JVM 上运行,那么您会被 RandomAccessFile 卡住。但是,您可以使用它来读取包含整个记录的byte[],然后使用ByteBuffer 从该数组中检索单个值。在最坏的情况下,您应该需要进行两次文件访问:一次检索记录的位置/大小,一次检索记录本身。

但是,请注意,如果您创建大量 byte[]s,您可能会开始向垃圾收集器施加压力,并且如果您在整个文件中反弹,您将保持 IO 绑定。

【讨论】:

  • @Anon 我当然不是这方面的专家,因此我对你为什么说如果它是 64 位机器内存映射文件是最好的方法感到非常感兴趣。您这么说是因为 32 位硬件架构的内存寻址限制还是任何其他特殊原因?
  • @edalorzo - 这是由于 32 位硬件的限制。在 64 位机器上,您的虚拟地址空间足够大,可以映射整个文件。在 32 位机器上,您必须不断地重新映射文​​件的某些部分,并且您可能会遇到 GC 问题(已映射的文件被垃圾收集器取消映射,它应该取消映射一个文件,以便您有空间映射另一个,但在这样做的时候可能会做一个完整的集合)。
  • 是的,我正在寻找类似您的 32 位解决方案的东西。看看我对 edalorzo 的评论。第一个问题是 mmmapping 许多不同的位置以进行小型读取(与 mmaping 的大小和成本相比)没有多大意义。
  • @marcorossi:当您阅读文件时,您不会映射文件的某些部分,而是映射整个文件。这可能会对您有所帮助:kdgcommons.svn.sourceforge.net/viewvc/kdgcommons/trunk/src/main/…
  • @kdgregory:这看起来很有趣,虽然我不能内存映射 100+GIG 文件。另外,您如何处理缓冲区之间的重叠数据?它不像你处理那个案子那样接缝。
【解决方案5】:

Apache PDFBox 项目有一个很好且经过测试的BufferedRandomAccessFile 类。
根据 Apache 许可证 2.0 版获得许可

它是 java.io.RandomAccessFile 类的优化版本,正如 Nick Zhang 在 JavaWorld.com 上所描述的那样。基于jmzreader 实现并增强以处理无符号字节。

在此处查看源代码:

  1. https://github.com/apache/pdfbox/.../fontbox/ttf/BufferedRandomAccessFile.java
  2. https://svn.apache.org/repos/asf/pdfbox/.../fontbox/ttf/BufferedRandomAccessFile.java

【讨论】:

    【解决方案6】:
    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;
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-10-08
      • 1970-01-01
      • 1970-01-01
      • 2011-08-20
      • 2012-05-11
      • 2011-02-20
      • 1970-01-01
      相关资源
      最近更新 更多