整理 Java I/O (十五):随机访问文件 - RandomAccessFile

Java 的 I/O 体系还有一个特殊的类,它虽然在 java.io 包里,却并不是 InputStream/OutputStream 的子类,它本身同时拥有读和写的能力。RandomAccessFile 的行为类似存在在文件中的一个大型的* 数组,有一个“指针”来表示数组的位置。输入操作从文件的“指针”开始读取字节,并随着对字节的读取而移动该“指针”。输出操作也是一样。该“指针”可以通过 getFilePointer 方法读取,并通过 seek 方法来设置。

我们可以根据“指针”的位置来进行指定位置的读取/写入。

构造方法

  • RandomAccessFile(File file,String mode)

    创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。

  • RandomAccessFile(String name,String mode)

    创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。

下表列出了 mode 值及其含义:

含义
"r"以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw"打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
"rws"打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
"rwd"打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。
  • 什么是元数据?
    元数据的定义是“关于数据的数据”或者叫做“用来描述数据的数据”或者叫做“信息的信息”。
    什么鬼玩意儿,其实可以简单的理解为最小的数据单位,譬如我们用手机拍摄一张照片,这张照片会存在一个“EXIF 信息”,这个 EXIF 信息就是用来描述一张数码图片的元数据,这些元数据包括“拍摄工具”、“拍摄时间”、“曝光时间”、“图片像素”等等一系列信息。

  • 各个模式之间的区别
    当操作的文件是存储在本地的基础存储设备上(如硬盘,NandFlash 等)时,“rws”,“rwd”或“rw”才有区别。

    • 当模式是“rws”并且操作的是基础存储设备上的文件时,每次更改文件内容或者修改文件元数据时,都会将这些改变同步到基础存储设备上。

    • 当模式是“rwd”并且操作的是基础存储设备上的文件时,每次更改文件内容时,都会将这些改变同步到基础设备上。

    • 当模式是“rw”并且操作的是基础存储设备上的文件时,关闭文件的时候,会将文件修改内容同步到基础存储设备上。

其他方法

  • close()

    关闭此随机访问文件流并释放与该流关联的所有系统资源。

  • getChannel()

    返回与此文件关联的唯一 FileChannel 对象。

  • getFD()

    返回与此流关联的不透明文件描述符对象。

  • getFilePointer()

    返回此文件中的当前偏移量。

  • length()

    返回此文件的长度。

  • read()

    从此文件中读取一个数据字节。

  • read(byte[] b)

    将最多 b.length 个数据字节从此文件读入 byte 数组。

  • read(byte[] b, int off, int len)

    将最多 len 个数据字节从此文件读入 byte 数组。

  • readBoolean()

    从此文件读取一个 boolean。

  • readByte()

    从此文件读取一个有符号的八位值。

  • readChar()

    从此文件读取一个字符。

  • readDouble()

    从此文件读取一个 double。

  • readFloat()

    从此文件读取一个 float。

  • readFully(byte[] b)

    将 b.length 个字节从此文件读入 byte 数组,并从当前文件指针开始。

  • readFully(byte[] b, int off, int len)

    将正好 len 个字节从此文件读入 byte 数组,并从当前文件指针开始。

  • readInt()

    从此文件读取一个有符号的 32 位整数。

  • readLine()

    从此文件读取文本的下一行。

  • readLong()

    从此文件读取一个有符号的 64 位整数。

  • readShort()

    从此文件读取一个有符号的 16 位数。

  • readUnsignedByte()

    从此文件读取一个无符号的八位数。

  • readUnsignedShort()

    从此文件读取一个无符号的 16 位数。

  • readUTF()

    从此文件读取一个字符串。

  • seek(long pos)

    设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。

  • setLength(long newLength)

    设置此文件的长度。

  • skipBytes(int n)

    尝试跳过输入的 n 个字节以丢弃跳过的字节。

  • write(byte[] b)

    将 b.length 个字节从指定 byte 数组写入到此文件,并从当前文件指针开始。

  • write(byte[] b, int off, int len)

    将 len 个字节从指定 byte 数组写入到此文件,并从偏移量 off 处开始。

  • write(int b)

    向此文件写入指定的字节。

  • writeBoolean(boolean v)

    按单字节值将 boolean 写入该文件。

  • writeByte(int v)

    按单字节值将 byte 写入该文件。

  • writeBytes(String s)

    按字节序列将该字符串写入该文件。

  • writeChar(int v)

    按双字节值将 char 写入该文件,先写高字节。

  • writeChars(String s)

    按字符序列将一个字符串写入该文件。

  • writeDouble(double v)

    使用 Double 类中的 doubleToLongBits 方法将双精度参数转换为一个 long,然后按八字节数量将该
    long 值写入该文件,先定高字节。

  • writeFloat(float v)

    使用 Float 类中的 floatToIntBits 方法将浮点参数转换为一个 int,然后按四字节数量将该 int 值写入该文件,先写高字节。

  • writeInt(int v)

    按四个字节将 int 写入该文件,先写高字节。

  • writeLong(long v)

    按八个字节将 long 写入该文件,先写高字节。

  • writeShort(int v)

    按两个字节将 short 写入该文件,先写高字节。

  • writeUTF(String str)

    使用 modified UTF-8 编码以与机器无关的方式将一个字符串写入该文件。

简单示例

写一个简单的例子,来对比一下直接使用 FileInputStream/FileOutputStream 去复制文件和使用 RandomAccessFile + 多个线程去复制文件的效率

public class RandomAccessFileTest {

    private FileInputStream fis;
    private FileOutputStream fos;

    private RandomAccessFile rasRead1;
    private RandomAccessFile rasRead2;
    private RandomAccessFile rasWrite1;
    private RandomAccessFile rasWrite2;

    public static void main(String[] args) {
        File file = new File("D:\\李宗盛 - 山丘.wav");
        System.out.println("文件长度: " + file.length() + " 字节");
        new RandomAccessFileTest().testFileStream(file, new File(
                "D:\\李宗盛 - 山丘 - 单线程.wav"));
        new RandomAccessFileTest().testRandomAccessFile(file, new File(
                "D:\\李宗盛 - 山丘 - 多线程.wav"));
    }

    private void testFileStream(File file, File newFile) {

        long time = System.currentTimeMillis();
        try {
            fis = new FileInputStream(file);
            fos = new FileOutputStream(newFile);
            int length = 0;
            byte[] buf = new byte[1024];
            while ((length = fis.read(buf)) != -1) {
                fos.write(buf, 0, length);
                fos.flush();
            }

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        System.out.println("单线程复制共耗时: " + (System.currentTimeMillis() - time)
                + " 毫秒");
    }

    private void testRandomAccessFile(File file, File newFile) {
        long time = System.currentTimeMillis();
        try {
            rasRead1 = new RandomAccessFile(file, "rw");
            rasRead2 = new RandomAccessFile(file, "rw");
            rasWrite1 = new RandomAccessFile(newFile, "rw");
            rasWrite2 = new RandomAccessFile(newFile, "rw");

            final long halfSize = file.length() / 2;
            long leftSize = file.length() - halfSize;

            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        int length = 0;
                        byte[] buf = new byte[1024];
                        while ((length = rasRead1.read(buf)) != -1) {
                            rasWrite1.write(buf, 0, length);
                            if (rasRead1.getFilePointer() >= halfSize) {
                                break;
                            }
                        }
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }).start();

            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        rasRead2.seek(halfSize);
                        rasWrite2.seek(halfSize);
                        int length = 0;
                        byte[] buf = new byte[1024];
                        while ((length = rasRead2.read(buf)) != -1) {
                            rasWrite2.write(buf, 0, length);
                        }

                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }).start();

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
//          try {
//              if (rasRead1 != null) {
//                  rasRead1.close();
//              }
//              if (rasRead2 != null) {
//                  rasRead2.close();
//              }
//              if (rasWrite1 != null) {
//                  rasWrite1.close();
//              }
//              if (rasWrite2 != null) {
//                  rasWrite2.close();
//              }
//          } catch (IOException e) {
//              // TODO Auto-generated catch block
//              e.printStackTrace();
//          }
        }
        System.out.println("多线程复制共耗时: " + (System.currentTimeMillis() - time)
                + " 毫秒");
    }
}

运行结果:

文件长度: 71583276 字节
单线程复制共耗时: 1411 毫秒
多线程复制共耗时: 42 毫秒

效率提高了好多吧~~

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1