今天学习RandomAccessFile。
RandomAccessFile,用来随机访问文件的类。它支持对随机访问文件的读取和写入。这里的随机并不意味着不可控制,而是意味着可以访问文件内容的任意位置。
随机访问文件是如何实现的呢?
随机访问文件是通过“文件指针”来实现的。文件指针可看做指向文件中任意位置的光标或索引。该文件指针可以通过getFilePointer方法读取,通过seek方法设置。输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入文件末尾之后的输出操作导致该数组扩展。
源码太长,放在最后,有兴趣的可以向后看。
demo
import java.io.File;
import java.io.RandomAccessFile;
import org.junit.Test;
import java.io.IOException;
/**
* RandomAccessFile demo
*/
public class RandomAccessFileTest {
private static final File file = new File("randomAccessFileTest.txt");
private void prepare() {
// 若文件存在,则删除该文件。目的是避免干扰。
if (file.exists())
file.delete();
}
// 测试seek、getFilePointer、length方法。打印结果为
/**
* 偏移量的设置超出文件末尾不会改变文件的长度。
* getFilePointer():54
* length():54
* getFilePointer():111111
* length():54
* 只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度。
*/
@Test
public void testSeek() {
prepare();
try {
RandomAccessFile raf = new RandomAccessFile(file, "rw");
raf.writeChars("abcdefghijklmnopqrstuvwxyz\n");
System.out.println("偏移量的设置超出文件末尾不会改变文件的长度。");
System.out.println("getFilePointer():" + raf.getFilePointer());
System.out.println("length():" + raf.length());
raf.seek(111111);
System.out.println("getFilePointer():" + raf.getFilePointer());
System.out.println("length():" + raf.length());
System.out.println("只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度。");
} catch (IOException e) {
e.printStackTrace();
}
}
//测试testSetLength方法。打印结果为
/**
* SetLength会改变文件大小,并没有改变下一个要写入的内容的位置。
* getFilePointer():54
* length():54
* getFilePointer():54
* length():111111
*/
@Test
public void testSetLength() {
prepare();
try {
RandomAccessFile raf = new RandomAccessFile(file, "rw");
raf.writeChars("abcdefghijklmnopqrstuvwxyz\n");
System.out.println("SetLength会改变文件大小,并没有改变下一个要写入的内容的位置。");
System.out.println("getFilePointer():" + raf.getFilePointer());
System.out.println("length():" + raf.length());
raf.setLength(111111l);
System.out.println("getFilePointer():" + raf.getFilePointer());
System.out.println("length():" + raf.length());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 向文件中写入内容
*/
@Test
public void testWrite() {
prepare();
try {
RandomAccessFile raf = new RandomAccessFile(file, "rw");
raf.writeChars("abcdefg\n");
raf.writeBoolean(true);
raf.writeByte(0x51);
raf.writeChar('h');
raf.writeShort(0x3c3c);
raf.writeInt(0x66);
raf.writeLong(0x123456789L);
raf.writeFloat(1.1f);
raf.writeDouble(1.111);
raf.writeUTF("潘威威的博客");
raf.writeChar('\n');
//追加内容
raf.seek(raf.length());
raf.write("追加的内容".getBytes());
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//读取文件并打印。打印结果为
/**
* readChar():a
* read(byte[],int,len):
*/
@Test
public void testRead() {
try {
RandomAccessFile raf = new RandomAccessFile(file, "r");
System.out.println("readChar():" + raf.readChar());
raf.skipBytes(2);//跳过两个字节,即跳过了'b'字符
byte[] buf = new byte[10];
raf.read(buf, 0, buf.length);
System.out.println("read(byte[],int,len):" + (new String(buf)));
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
源码
下面是观看源码的总结。
RandomAccessFile并没有继承InputStream或者OutputStream,而是实现了DataOutput和DataInput。这说明它可以操作Java基础数据类型,而且既可读,也可写。
打开文件的访问模式有四种:
- “r”——以只读方式打开。调用结果对象的任何write方法都将导致抛出IOException。
- “rw”——打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
- “rws”——打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
- “rwd”——打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。
一般使用前两个模式。
RandomAccessFile中读写Java基本数据类型的方法实现和DataInputStream与DataOutputStream极其相似,可以参考Java8 I/O源码-DataInputStream与DataOutputStream一文了解这些方法的实现。
/**
* RandomAccessFile并没有继承InputStream或者OutputStream,而是实现了DataOutput和DataInput。
* 这说明它可以操作Java基础数据类型,而且既可读,也可写。
*
*/
public class RandomAccessFile implements DataOutput, DataInput, Closeable {
//文件描述符
private FileDescriptor fd;
//文件通道
private FileChannel channel = null;
//标识此文件是否既可以读又可以写
private boolean rw;
/**
* 引用文件的路径
*/
private