文章目录
-
- 1. 概念
- 2. 字段
- 3. 方法
-
- 1. 构造器
- 2. 两个重要的方法
- 3. 其他方法
- 4. 案例
1. 概念
RandomAccessFile 既可以读取文件内容,也可以向文件输出数据。翻看源码可知 RandomAccessFile 继承了 DataOutput 和DataInput 两个接口,而字节流体系中 DataOutputStream 和 DataInputStream 也分别继承了 DataOutput 接口和 DataInput 接口,所以 RandomAccessFile 类中包含了 DataOutputStream 和 DataInputStream 类中所有方法,有些方法直接调用 “字节数据流” 中方法,如其中 WriteUTF() 方法直接调用 DataOutputStream 类中 WriteUTF() 方法,ReaderUTF() 方法直接调用 DataInputStream 类中 ReadUTF() 方法;
同时,RandomAccessFile 支持 随机访问 的方式,程序快可以直接跳转到文件的任意地方来读写数据。原理大概就是:将文件看成是一个大型的字节数组,通过游标(cursor)或者移动文件指针对数组中任意位置字节读取或者写入,从而达到对文件进行随机访问的目的;由于 RandomAccessFile 可以自由访问文件的任意位置,所以如果需要访问文件的部分内容,而不是把文件从头读到尾,使用 RandomAccessFile 将是更好的选择。
与 OutputStream、Writer 等输出流不同的是,RandomAccessFile 允许自由定义文件记录指针,RandomAccessFile 可以不从开始的地方开始输出,因此 RandomAccessFile 可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用 RandomAccessFile。
RandomAccessFile 的方法虽然多,但它有一个最大的局限,就是只能读写文件,不能读写其他 IO 节点。
RandomAccessFile 的一个重要使用场景就是网络请求中的多线程下载及断点续传。
2. 字段
- private FileDescriptor fd:与流关联的文件描述符对象
- private FileChannel channel = null:读取文件的通道
- private boolean rw:是否有读写的权限
- private final String path:文件的路径
- private Object closeLock = new Object():文件关闭对象锁
- private volatile boolean closed = false:流是否关闭的标识
- private static final int O_RDONLY = 1:对应读写模式中"r"
- private static final int O_RDWR = 2:对应读写模式中"rw"
- private static final int O_SYNC = 4:对应读写模式中"rws"
- private static final int O_DSYNC = 8:对应读写模式中"rwd"
3. 方法
1. 构造器
- RandomAccessFile(String name, String mode)
- RandomAccessFile(File file, String mode)
RandomAccessFile 类有两个构造函数,其实这两个构造函数基本相同,只不过是指定文件的形式不同:一个需要使用 String 参数来指定文件名,一个使用 File 参数来指定文件本身。除此之外,创建 RandomAccessFile 对象时还需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式,一共有 4 种模式:
- “r”:以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException
- “rw”:以读写方式打开,以便读取和写入
- “rws”:以读写方式打开,以便读取和写入。相对于 “rw”,“rws” 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备
- “rwd”:以读写方式打开,以便读取和写入,相对于 “rw”,“rwd” 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。
2. 两个重要的方法
RandomAccessFile 既可以读文件,也可以写文件,所以类似于 InputStream 的 read() 方法,以及类似于 OutputStream 的 write() 方法,RandomAccessFile 都具备。除此之外,RandomAccessFile 具备两个特有的方法,来支持其随机访问的特性。
RandomAccessFile 对象包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个 RandomAccessFile 对象时,该对象的文件指针记录位于文件头(也就是 0 处),当读/写了 n 个字节后,文件记录指针将会后移 n 个字节。除此之外,RandomAccessFile 还可以自由移动该记录指针。下面就是 RandomAccessFile 具有的两个特殊方法,来操作记录指针,实现随机访问:
- long getFilePointer( ):返回文件记录指针的当前位置
- void seek(long pos ):将文件指针定位到 pos 位置
3. 其他方法
- public final FileDescriptor getFD():返回与流关联的文件描述符对象
- public final FileChannel getChannel():返回与文件关联的通道
- public int read():读取单个字节
- public int read(byte b[], int off, int len):从文件读取最多len个字节到字节数组b中
- public int read(byte b[]):从文件中读取最多b.length个字节到字节数组b中
- public final void readFully(byte b[]):从当前文件指针的位置开始读"满" b.length 个字节到字节数组b中,在读取b.length个字节之前,方法一直阻塞,除非流结束或者抛出异常
- public final void readFully(byte b[], int off, int len):从文件中当前文件指针开始读"满"len个字节到字节数组b中.在读取len个字节之前,方法一直阻塞,除非流结束或者抛出异常
- public int skipBytes(int n):跳过n个字节,可能存在由于文件中剩余可读取的字节数比要跳过字节n小,导致实际跳过字节小于n
- public void write(int b):从当前文件指针开始,写入一个字节到文件中
- public void write(byte b[]):将字节数组b写到文件中
- public void write(byte b[], int off, int len):将字节数组b中off位置开始,len个字节写到文件中
- public native long getFilePointer():返回文件中当前偏移量,即当前文件指针位置
- public void seek(long pos):设置文件指针偏移量.表示的是文件中下一个读取数据位置
- public native long length():返回文件长度(针对字节)
- public native void setLength(long newLeng):设置文件的长度,存在两种情况: 1.文件长度length>参数newLength,文件将会被截断,调用此方法前,getFilePointer()>newLength,调用此方法后offset=newlength. 2.文件长度length<参数newLenght,文件将会扩展,扩展部分的内容未定义
- public void close(){}:关闭流,释放关联资源
以下这些方法与DataInputStream和DataOutputStream中方法一样,有的是直接调用DataOutputStream和DataInputStream中的方法:
- public final boolean readBoolean(){}
- public final byte readByte(){}
- public final int readUnsignedByte(){}
- public final short readShort(){}
- public final int readUnsignedShort(){}
- public final char readChar(){}
- public final int readInt(){}
- public final long readLong(){}
- public final float readFloat(){}
- public final double readDouble(){}
- public final String readLine(){}
- public final String readUTF(){}
- public final void writeBoolean(boolean v)
- public final void writeByte(int v){}
- public final void writeShort(int v){}
- public final void writeChar(int v){}
- public final void writeInt(int v){}
- public final void writeLong(long v){}
- public final void writeFloat(float v){}
- public final void writeDouble(double v){}
- public final void writeChars(String s){}
- public final void writeUTF(String str){}
4. 案例
案例1:利用 RandomAccessFile 实现文件的多线程下载,即多线程下载一个文件时,将文件分成几块,每块用不同的线程进行下载。下面是一个利用多线程在写文件时的例子,其中预先分配文件所需要的空间,然后在所分配的空间中进行分块,然后写入
/** * 测试利用多线程进行文件的写操作 */
public class Test {
public static void main(String[] args) throws Exception {
// 预分配文件所占的磁盘空间,磁盘中会创建一个指定大小的文件 RandomAccessFile raf = new RandomAccessFile("D://abc.txt", "rw"); raf.setLength(1024*1024); // 预分配 1M 的文件空间 raf.close(); // 所要写入的文件内容 String s1 = "第一个字符串"; String s2 = "第二个字符串"; String s3 = "第三个字符串"; String s4 = "第四个字符串"; String s5 = "第五个字符串"; // 利用多线程同时写入一个文件 new FileWriteThread(1024*1,s1.getBytes()).start(); // 从文件的1024字节之后开始写入数据 new FileWriteThread(1024*2,s2.getBytes()).start(); // 从文件的2048字节之后开始写入数据 new FileWriteThread(1024*3,s3.getBytes()).start(); // 从文件的3072字节之后开始写入数据 new FileWriteThread(1024*4,s4.getBytes()).start(); // 从文件的4096字节之后开始写入数据 new FileWriteThread(1024*5,s5.getBytes()).start(); // 从文件的5120字节之后开始写入数据 } // 利用线程在文件的指定位置写入指定数据 static class FileWriteThread extends Thread{
private int skip; private byte[] content; public FileWriteThread(int skip,byte[] content){
this.skip = skip; this.content = content; } public void run(){
RandomAccessFile raf = null; try {
raf = new RandomAccessFile("D://abc.txt", "rw"); raf.seek(skip); raf.write(content); } catch (FileNotFoundException e) {
e.printStackTrace(); } catch (IOException e) {
// TODO Auto-generated catch block e.printStackTrace(); } finally {
try {
raf.close(); } catch (Exception e) {
} } } }
}
当 RandomAccessFile 向指定文件中插入内容时,将会覆盖掉原有内容。如果不想覆盖掉,则需要将原有内容先读取出来,然后先把插入内容插入后再把原有内容追加到插入内容后。
案例2:
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
String fileName ="D:\\java.txt";RandomAccessFile asf = new RandomAccessFile(fileName,"rw");for(int i = 0 ; i < 5;i++){
asf.writeDouble(1.414*i);}asf.writeUTF("this is demo of RandomAccessFile");displayFileContent(fileName);asf.seek(4*8);//double类型是8个字节,指针指向第4个double末尾,第5个开头,写入数据覆盖第5个.asf.writeDouble(12.345);displayFileContent(fileName);asf.close();long length = testRandomAccessFileWriter(fileName);//返回的是开始写入java基本数据类型的位置.testRandomAccessFileReader(fileName,length);//通过length找到相应的位置用对应方法读取.}private static void displayFileContent(String fileName) throws IOException{
RandomAccessFile asf = new RandomAccessFile(fileName,"rw");for(int i =0 ;i < 5;i++){
System.out.println(i+"--------------"+asf.readDouble());}String readUTF = asf.readUTF();System.out.println(readUTF);asf.close();}private static long testRandomAccessFileWriter(String fileName) throws IOException{
RandomAccessFile asf = new RandomAccessFile(fileName,"rw");long length = asf.length();asf.seek(length);asf.write(2);asf.writeChar('a');asf.writeBoolean(true);asf.writeShort(1234);asf.writeInt(123456);asf.writeLong(123456789);asf.writeFloat(1.732f);asf.writeChars("abcdefghijk");asf.close();return length;}private static void testRandomAccessFileReader(String fileName,long length) throws IOException{
RandomAccessFile asf = new RandomAccessFile(fileName,"r");asf.seek(length);System.out.println(asf.readByte());System.out.println(asf.readChar());System.out.println(asf.readBoolean());System.out.println(asf.readShort());System.out.println(asf.readInt());System.out.println(asf.readLong());System.out.println(asf.readFloat());System.out.println(asf.readLine());asf.close();}
}
//结果
this is demo of RandomAccessFile
0--------------0.0
1--------------1.414
2--------------2.828
3--------------4.242
4--------------5.656
this is demo of RandomAccessFile
0--------------0.0
1--------------1.414
2--------------2.828
3--------------4.242
4--------------12.345 // 利用了 seek 覆盖了数据
this is demo of RandomAccessFile
2
a
true
1234
123456
123456789
1.732