RandomAccessFile详解

        RandomAccessFile是Java 输入/输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,它既可以读取文件内容,也可以向文件输出数据。与普通的输入/输出流不同的是,RandomAccessFile支持"随机访问"的方式,程序可以直接跳转到文件的任意地方来读写数据。

  1. RandomAccessFile可以自由访问文件的任意位置
  2. RandomAccessFile允许自由定位文件记录指针
  3. RandomAccessFile只能读写文件而不是流。

        RandomAccessFile类中包含一个游标(文件指针:隐含数组的索引),用以标识当前读写处的位置(文件指针开始位于文件头(也就是0处),当读/写了n个字节后,文件记录指针将会向后移动n个字节。),RandomAccessFile可以自由移动该游标。

RandomAccessFile包含了如下两个方法来操作文件记录指针。
        ➢ long getFilePointer():返回文件记录指针的当前位置。(native方法)
        ➢ void seek(long pos):将文件记录指针定位到pos位置。(调用本地方法seek0)

RandomAccessFile类有两个构造器:

  1. RandomAccessFile(String name, String mode):
  2. RandomAccessFile(File file, String mode)

创建一个随机访问文件的流:

  (1)构造器1中name会转换为构造器2中的file,RandomAccessFile(String name, String mode)等价于RandomAccessFile(new File(name), String mode)

  (2)mode – 访问模式
➢ "r":以只读方式打开指定文件。如果试图对该RandomAccessFile执行写入方法,都将抛出IOException异常。
➢ "rw":以读、写方式打开指定文件。如果该文件尚不存在,则尝试创建该文件。
➢ "rws":以读、写方式打开指定文件。相对于"rw"模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
➢ "rwd":以读、写方式打开指定文件。相对于"rw"模式,还要求对文件内容的每个更新都同步写入到底层存储设备。

        "rws"和"rwd"模式的工作方式与FileChannel类的force(boolean)方法非常相似,传递true和false两种参数来实现,但它们始终适用于每个 I/O 操作,因此通常更有效。 如果文件驻留在本地存储设备上,则当此类方法的调用返回时,可以保证该调用对文件所做的所有更改都将写入该设备。 这对于确保在系统崩溃时不会丢失关键信息非常有用。 如果文件不在本地设备上,则不提供此类保证。

        “rwd”模式可用于减少执行的 I/O 操作的数量。 使用“rwd”只需要更新要写入存储的文件内容; 使用“rws”需要更新文件内容及其要写入的元数据,这通常至少需要再进行一次低级 I/O 操作。
        如果有安全管理器,则调用其checkRead方法,并以file参数的路径名作为其参数,以查看是否允许对文件进行读访问。 如果模式允许写入,还会使用路径参数调用安全管理器的checkWrite方法,以查看是否允许对文件进行写访问。

        RandomAccessFile类的read()方法和write()方法和流的操作执行上没有太大区别。

注意:
        RandomAccessFile依然不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输出,则新输出的内容会覆盖文件中原有的内容。如果需要向指定位置插入内容,程序需要先把插入点后面的内容读入缓冲区,等把需要插入的数据写入文件后,再将缓冲区的内容追加到文件后面。

    /**
     * 向指定文件的指定位置插入指定的内容
     *
     * @param fileName      指定文件名
     * @param pos           指定文件的指定位置
     * @param insertContent 指定文件的指定位置要插入的指定内容
     */
    public static void insert(String fileName, long pos,
                              String insertContent) throws IOException {
        RandomAccessFile raf = null;
        //创建一个临时文件来保存插入点后的数据
        File tmp = File.createTempFile("tmp", null);
        FileOutputStream tmpOut = null;
        FileInputStream tmpIn = null;
        tmp.deleteOnExit();
        try {
            raf = new RandomAccessFile(fileName, "rw");
            tmpOut = new FileOutputStream(tmp);
            tmpIn = new FileInputStream(tmp);
            raf.seek(pos);
            //--------下面代码将插入点后的内容读入临时文件中保存---------
            byte[] bbuf = new byte[64];
            //用于保存实际读取的字节数
            int hasRead = 0;
            //使用循环方式读取插入点后的数据
            while ((hasRead = raf.read(bbuf)) > 0) {
                //将读取的数据写入临时文件
                tmpOut.write(bbuf, 0, hasRead);
            }
            //----------下面代码插入内容----------
            //把文件记录指针重新定位到pos位置
            raf.seek(pos);
            //追加需要插入的内容
            raf.write(insertContent.getBytes());
            //追加临时文件中的内容
            while ((hasRead = tmpIn.read(bbuf)) > 0) {
                raf.write(bbuf, 0, hasRead);
            }
        } finally {
            if (raf != null) {
                raf.close();
            }
        }
    }

RandomAccessFile实例中的方法详解:

  1. public final FileDescriptor getFD() throws IOException:返回该RandomAccessFile对象的文件描述符对象,FileDescriptor对象相当于与操作系统交互需要时产生的特定结构的不透明句柄,表示打开的文件、打开的套接字或另一个字节源或接收器。
    (因为java中只有对象的概念,即一个文件就是一个对象,但是为了便于对文件操作需要文件描述符(即文件索引:可以快速定位文件,查看当前文件偏移量)这一结构,但文件描述符对于方法调用者来说往往是不需要知道的,故它相当于一个不透明的句柄。)
  2. public final FileChannel getChannel() :返回该RandomAccessFile对象关联的文件的唯一FileChannel对象。
    (返回的通道的position将始终等于getFilePointer方法返回的该对象的文件指针偏移量,这正是FileDescriptor发挥的作用;返回的FileChannel对象是唯一的。(单例模式))
  3. public int read() throws IOException:从此文件中读取一个字节的数据。 该字节作为 0 到 255 ( 0x00-0x0ff ) 范围内的整数返回。 如果尚无可用输入,则此方法会阻塞。
    (
    该read方法与InputStream.read()方式是完全相同的,都是调用本地方法read0();
    
    返回值:数据的下一个字节,如果已到达文件末尾,则为-1 。
    )
  4. public int read(byte b[], int off, int len) throws IOException:从此文件中读取最多len个字节的数据到一个字节数组中。 此方法会阻塞,直到至少有一个字节的输入可用。
    (
    该read方法与InputStream.read(byte[], int, int)方式是完全相同的,调用本地方法readBytes(byte b[], int off, int len);
    
    返回值:数据的下一个字节,如果已到达文件末尾,则为-1 ;
    
    参数:
    b – 读取数据的缓冲区。
    off – 数组b中写入数据的起始偏移量。
    len – 读取的最大字节数。
    
    返回值:
    读入缓冲区的总字节数,如果由于已到达文件末尾而没有更多数据,则为-1 。
    
    IOException – 如果由于文件结尾以外的任何原因无法读取第一个字节,或者如果随机访问文件已关闭,或者如果发生其他一些 I/O 错误。
    )
  5. public int read(byte b[]) throws IOException :相当于read(byte b[], 0, byte.length)。
  6. public final void readFully(byte b[], int off, int len) throws IOException:从当前文件指针开始,从该文件中准确读取len个字节到字节数组中。 此方法从文件中重复读取,直到读取了请求的字节数。 此方法会阻塞,直到读取了请求的字节数、检测到流的结尾或抛出异常。
    readFully()方法适用于读取特定长度的严格的数据,当缓冲区未满时会阻塞等待数据传入继续读取,直至缓冲区填满;而read()方法无论缓冲区是否填满,如果在缓冲区填满之前读到文件结尾也会直接结束读取。
  7. public final void readFully(byte b[]) throws IOException:相当于readFully(byte b[], 0, byte.length)。
  8. public int skipBytes(int n) throws IOException:尝试跳过n个字节的输入并丢弃跳过的字节。(本质是使用seeek()方法设置文件偏移量指针的位置,并且会判断文件的实际长度,即不会抛出EOFException。)
  9. public void write(int b) throws IOException:调用本地方法write0(b)将指定的字节写入此文件。 写入从当前文件指针开始。
  10. public void write(byte b[], int off, int len) throws IOException:调用本地方法writeBytes(byte b[], int off, int len)将指定字节数组中的len个字节从 offset off写入此文件。
    (
    参数:
    b – 要写入的数据
    off - 数据中的起始偏移量
    len – 写入的字节数
    )
  11. public void write(byte b[]) throws IOException:相当于write(b, 0, b.length)。
  12. public native long getFilePointer() throws IOException:返回此文件中的当前偏移量。
  13. public void seek(long pos) throws IOException:调用本地方法seek0(long pos)设置文件指针偏移量,从该文件的开头开始测量,在该位置发生下一次读取或写入。 偏移量可能设置在文件末尾之外。 设置超出文件末尾的偏移量不会改变文件长度。 只有在将偏移量设置为超出文件末尾之后,文件长度才会更改。
  14. public native long length() throws IOException:返回此文件的长度,以字节为单位。
  15. public native void setLength(long newLength) throws IOException:设置此文件的长度。(
    如果length方法返回的文件的当前长度大于newLength参数,则文件将被截断。 在这种情况下,如果getFilePointer方法返回的文件偏移量大于newLength则在此方法返回后偏移量将等于newLength 。
    如果length方法返回的文件的当前长度小于newLength参数,则文件将被扩展。 在这种情况下,未定义文件扩展部分的内容。
    )
    
  16. public void close() throws IOException:关闭此随机访问文件流并释放与该流关联的任何系统资源。 关闭的随机访问文件无法执行输入或输出操作,也无法重新打开。(这个方法是线程安全的。)
  17. 一些见名知义的方法(偏移量的改变取决于这些方法的返回值类型所需要的字节数):
     
    boolean readBoolean():从此文件中读取一个boolean。
    
    byte readByte():从此文件中读取一个有符号的八位值。
    
    int readUnsignedByte():从此文件中读取一个无符号的八位数字。
    
    short readShort():从此文件中读取一个带符号的 16 位数字。
    
    int readUnsignedShort():从此文件中读取一个无符号的 16 位数字。
    
    char readChar():从此文件中读取一个字符。 此方法从文件中读取两个字节,从当前文件指针开始。 
    
    int readInt():从此文件中读取一个有符号的 32 位整数。 此方法从文件中读取 4 个字节,从当前文件指针开始。
    
    long readLong():从此文件中读取一个有符号的 64 位整数。 此方法从文件中读取八个字节,从当前文件指针开始。
    
    float readFloat():从此文件中读取float 。 此方法读取的int值,并从当前文件指针,仿佛通过readInt方法,然后,其将int到float使用intBitsToFloat在类方法Float。
    
    double readDouble():从此文件中读取double精度值。 此方法读取一个long值,从当前文件指针开始,就像通过readLong方法一样,然后使用Double类中的longBitsToDouble方法将该long转换为double 。
    
    String readLine():(常用)从此文件中读取下一行文本。 此方法从文件中连续读取字节,从当前文件指针开始,直到到达行终止符或文件末尾。 通过取字符低八位的字节值并将字符的高八位设置为零,将每个字节转换为字符。 因此,此方法不支持完整的 Unicode 字符集。
    
    String readUTF():从此文件中读入一个字符串。 该字符串已使用修改后的 UTF-8格式进行编码。(从当前文件指针开始读取前两个字节,就像readUnsignedShort 。 该值给出了编码字符串中的后续字节数,而不是结果字符串的长度。 然后将以下字节解释为修改后的 UTF-8 格式中的字节编码字符并转换为字符。)

    以上所有读取方法都会阻塞线程


    void writeBoolean(boolean v):将boolean作为一字节值写入文件。 值true作为值(byte)1写出; 值false作为值(byte)0写出。 写入从文件指针的当前位置开始。
    
    void writeByte(int v):将一个byte作为一字节值写入文件。 写入从文件指针的当前位置开始。
    
    void writeShort(int v):以两个字节的形式将short写入文件,高字节在前。 写入从文件指针的当前位置开始。
    
    void writeChar(int v):将一个char作为两字节值写入文件,高字节在前。 写入从文件指针的当前位置开始。
    
    void writeInt(int v):将int作为四个字节写入文件,高字节在前。 写入从文件指针的当前位置开始。
    
    void writeLong(long v):将long写入文件为 8 个字节,高字节在前。 写入从文件指针的当前位置开始。
    
    void writeFloat(float v):使用Float类中的floatToIntBits方法将 float 参数转换为int ,然后将该int值作为四字节数量写入文件,高字节在前。 写入从文件指针的当前位置开始。
    
    void writeDouble(double v):使用Double类中的doubleToLongBits方法将 double 参数转换为long ,然后将该long值作为八字节数量写入文件,高字节在前。 写入从文件指针的当前位置开始。
    
    @SuppressWarnings("deprecation")
    void writeBytes(String s):将字符串作为字节序列写入文件。 通过丢弃其高八位,按顺序写出字符串中的每个字符。 写入从文件指针的当前位置开始。
    
    void writeChars(String s):将字符串作为字符序列写入文件。 每个字符都被写入数据输出流,就像通过writeChar方法一样。 写入从文件指针的当前位置开始。
    
    void writeUTF(String str):以独立于机器的方式使用修改后的 UTF-8编码将字符串写入文件。
    (首先,从当前文件指针开始,将两个字节写入文件,就像通过writeShort方法给出要跟随的字节数一样。 该值是实际写出的字节数,而不是字符串的长度。 按照长度,字符串的每个字符按顺序输出,对每个字符使用修改后的 UTF-8 编码。)