RandomAccessFile是一个随机访问文件即可读又可写的类,包含从文件读取数据的方法,又包含了向文件写入数据的方法.但不在IO流中字符流和字节流继承体系中.是完全独立的一个类.翻看源码可知RandomAccessFile继承了DataOutput和DataInput两个接口,而字节流体系中DataOutputStream和DataInputStream也分别继承了DataOutput接口和DataInput接口.所以RandomAccessFile类中包含了DataOutputStream和DataInputStream类中所有方法,有些方法直接调用"字节数据流"中方法.如其中WriteUTF()方法直接调用DataOutputStream类中WriteUTF()方法和ReaderUTF()方法直接调用DataInputStream类中ReadUTF()方法.
综上,RandomAccessFile继承DataOutput和DataInput接口,拥有读取和写入java基本数据类型和UTF-8字符串方法,有效地与IO流继承体系中其他部分实现了分离,由于不支持装饰,所以不能与OutputStream和InputStream的子类结合起来使用.RandomAccessFile之所以说对文件随机访问,是将文件看成是一个大型的字节数组.通过游标(cursor)或者移动文件指针理解为数组中索引对数组中任意位置字节读取或者写入.从而做到对文件的随机访问.RandomAccessFile构造方法中会传入对应的读写模式,共有4种.如下:
读写模式 |
解释 |
"r" |
以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException. |
"rw" |
打开以便读取和写入. |
"rws" |
打开以便读取和写入。相对于 "rw","rws" 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备. |
"rwd" |
打开以便读取和写入,相对于 "rw","rwd" 还要求对“文件的内容”的每个更新都同步写入到基础存储设备. |
其中"元数据"(metadata)理解为描述数据的数据.即描述数据的属性信息.比如对于文件来说,有文件创建时间,文件修改时间,文件读取权限等等.
1.构造方法
public RandomAccessFile(String name, String mode){}
public RandomAccessFile(File file, String mode){}
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;
private static final int O_RDWR = 2;
private static final int O_SYNC = 4;
private static final int O_DSYNC = 8;
3.内部方法
public final FileDescriptor getFD(){}
public final FileChannel getChannel(){}
public int read(){}
public int read(byte b[], int off, int len){}
public int read(byte b[]) {}
public final void readFully(byte b[]){}
public final void readFully(byte b[], int off, int len){}
public int skipBytes(int n){}
public void write(int b) {}
public void write(byte b[]){}
public void write(byte b[], int off, int len){}
public native long getFilePointer()
public void seek(long pos){}
public native long length(){}
public native void setLength(long newLength){}
public void close(){}
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){}
其余方法与DataInputStream和DataOutputStream中方法一样,有的是直接调用DataOutputStream和DataInputStream中的方法,比如WriteUTF和ReadUTF().
修饰符 |
写入方法 |
读取方法 |
功能 |
---|---|---|---|
public final |
boolean readBoolean(){} |
void writeBoolean(boolean v){} |
读取和写入布尔类型数据 |
public final |
byte readByte(){} |
void writeByte(int v){} |
读取和写入单个字节数据 |
public final |
int readUnsignedByte(){} |
- |
读取无符号的单个字节数据 |
public final |
short readShort(){} |
void writeShort(int v){} |
读取和写入short类型数据 |
public final |
int readUnsignedShort(){} |
- |
读取无符号的short类型数据 |
public final |
char readChar(){} |
void writeChar(int v){} |
读取和写入char类型数据 |
public final |
int readInt(){} |
void writeInt(int v){} |
读取和写入int类型数据 |
public final |
long readLong(){} |
void writeLong(long v){} |
读取和写入long类型数据 |
public final |
float readFloat(){} |
void writeFloat(float v){} |
读取和写入float类型数据 |
public final |
double readDouble(){} |
void writeDouble(double v){} |
读取和写入double类型数据 |
public final |
String readLine(){} |
- |
读取一行数据 |
public final |
String readUTF(){} |
void writeUTF(String str){} |
读取UTF-8解码后数据.写入UTF-8编码后数据 |
public final |
- |
void writeChars(String s){} |
将字符串s中每个字符转换成字节写到文件中 |
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();
}
}
运行结果:
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
this is demo of RandomAccessFile
2
a
true
1234
123456
123456789
1.732
public class RandomAccessFile implements DataOutput, DataInput, Closeable {
//文件描述符
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"
//创建的是指定了文件名称和读取模式mode的RandomAccessFile流.其中读取模式如下几种
/**
"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
"rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
"rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。
*/
public RandomAccessFile(String name, String mode)
throws FileNotFoundException
{
this(name != null ? new File(name) : null, mode);
}
//创建指定了文件对象File和读写模式mode的RandomAccessFile流.
public RandomAccessFile(File file, String mode)
throws FileNotFoundException
{
String name = (file != null ? file.getPath() : null);
int imode = -1;
if (mode.equals("r"))
imode = O_RDONLY;
else if (mode.startsWith("rw")) {
imode = O_RDWR;
rw = true;
if (mode.length() > 2) {
if (mode.equals("rws"))
imode |= O_SYNC;
else if (mode.equals("rwd"))
imode |= O_DSYNC;
else
imode = -1;
}
}
if (imode < 0)
throw new IllegalArgumentException("Illegal mode \"" + mode
+ "\" must be one of "
+ "\"r\", \"rw\", \"rws\","
+ " or \"rwd\"");
SecurityManager security = System.getSecurityManager();
if (security != null) {
//检查读取权限
security.checkRead(name);
if (rw) {
//检查读写权限
security.checkWrite(name);
}
}
if (name == null) {
throw new NullPointerException();
}
//文件路径有效性
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
//文件描述符对象中保存流引用
fd.attach(this);
path = name;
open(name, imode);
}
//返回与流关联的文件描述符对象
public final FileDescriptor getFD() throws IOException {
if (fd != null) {
return fd;
}
throw new IOException();
}
//返回与文件关联的通道.
public final FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, path, true, rw, this);
}
return channel;
}
}
private native void open0(String name, int mode)
throws FileNotFoundException;
//封装本地方法,打开文件,获取文件描述符
//其中mode传入O_RDWR时,可读写操作,否则只能进行读取操作
//name路径指代文件夹时,将抛出异常
private void open(String name, int mode)
throws FileNotFoundException {
open0(name, mode);
}
//读取单个字节(即十进制是0-255,十六进制是0x00-0x0ff)
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
private native int readBytes(byte b[], int off, int len) throws IOException;
//从文件读取最多len个字节到字节数组b中,b中从off位置开始.
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}
//从文件中读取最多b.length个字节到字节数组b中.
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
//从文件中当前文件指针开始读取b.length个字节到字节数组b中
//方法将会重复从文件中"读满"b.length个字节.
//在读取b.length个字节之前,方法一直阻塞,除非流结束或者抛出异常.
public final void readFully(byte b[]) throws IOException {
readFully(b, 0, b.length);
}
//从文件中当前文件指针开始读取len个字节到字节数组b中
//方法将会重复从文件中"读满"len个字节.
//在读取len个字节之前,方法一直阻塞,除非流结束或者抛出异常
public final void readFully(byte b[], int off, int len) throws IOException {
int n = 0;
do {
int count = this.read(b, off + n, len - n);
if (count < 0)
throw new EOFException();
n += count;
} while (n < len);
}
//跳过n个字节.n为负数情况,不会跳过任何字节.
//可能存在由于文件中剩余可读取的字节数比要跳过字节n小,导致实际跳过字节小于n
public int skipBytes(int n) throws IOException {
long pos;
long len;
long newpos;
if (n <= 0) {
return 0;
}
pos = getFilePointer();
len = length();//文件长度
newpos = pos + n;
//表示剩余可读字节少于n,实际只能跳过newpos-pos
if (newpos > len) {
newpos = len;
}
seek(newpos);
//返回实际跳过的字节数
return (int) (newpos - pos);
}
//从当前文件指针开始,写入一个字节到文件中
public void write(int b) throws IOException {
write0(b);
}
private native void write0(int b) throws IOException;
private native void writeBytes(byte b[], int off, int len) throws IOException;
//将字节数组b写到文件中.
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length);
}
//将字节数组b中off位置开始,len个字节写到文件中.
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len);
}
//返回文件中当前偏移量,即当前文件指针位置.
public native long getFilePointer() throws IOException;
//设置文件指针偏移量.表示的是文件中下一个读取数据位置.
//值可能设置超过文件长度,但不会改变文件长度
public void seek(long pos) throws IOException {
if (pos < 0) {
throw new IOException("Negative seek offset");
} else {
seek0(pos);
}
}
private native void seek0(long pos) throws IOException;
//返回文件长度(针对字节)
public native long length() throws IOException;
//设置文件的长度,存在两种情况:
//1.文件长度length>参数newLength,文件将会被截断,调用此方法前,getFilePointer()>newLength,调用此方法后offset=newlength
//2.文件长度length<参数newLenght,文件将会扩展,扩展部分的内容未定义
public native void setLength(long newLength) throws IOException;
//关闭流,释放关联的资源
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
/**
* 下面一些读取和写入方法直接调用DataOutputStream和DataInputStream方法实现
*
*/
//从文件的当前文件指针读取一个布尔类型数据.所以一次性会读取1个字节
//在读取1个字节之前,方法会一直阻塞,除非流已经结束或者有异常抛出
public final boolean readBoolean() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return (ch != 0);
}
//从文件的当前文件指针读取单个字节数据.所以一次性会读取1个字节
//在读取1个字节之前,方法会一直阻塞,除非流已经结束或者有异常抛出
public final byte readByte() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return (byte)(ch);
}
//从文件的当前文件指针读取无符号单个字节数据.所以一次性会读取1个字节
//在读取1个字节之前,方法会一直阻塞,除非流已经结束或者有异常抛出
public final int readUnsignedByte() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return ch;
}
//从文件的当前文件指针读取一个无符号short类型数据(short占用2个字节).所以一次性会读取2个字节
//在读取2个字节之前,方法会一直阻塞,除非流已经结束或者有异常抛出
public final short readShort() throws IOException {
int ch1 = this.read();
int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (short)((ch1 << 8) + (ch2 << 0));
}
//从文件的当前文件指针读取一个无符号short类型数据(short占用2个字节).所以一次性会读取2个字节
//在读取2个字节之前,方法会一直阻塞,除非流已经结束或者有异常抛出
public final int readUnsignedShort() throws IOException {
int ch1 = this.read();
int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (ch1 << 8) + (ch2 << 0);
}
//从文件的当前文件指针读取一个char类型数据(char占用2个字节).所以一次性会读取2个字节
//在读取2个字节之前,方法会一直阻塞,除非流已经结束或者有异常抛出
public final char readChar() throws IOException {
int ch1 = this.read();
int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (char)((ch1 << 8) + (ch2 << 0));
}
//从文件的当前文件指针读取一个int类型数据(int占用4个字节).所以一次性会读取4个字节
//在读取4个字节之前,方法会一直阻塞,除非流已经结束或者有异常抛出
public final int readInt() throws IOException {
int ch1 = this.read();
int ch2 = this.read();
int ch3 = this.read();
int ch4 = this.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}
//从文件的当前文件指针读取一个long类型数据(long占用8个字节).所以一次性会读取8个字节
//在读取8个字节之前,方法会一直阻塞,除非流已经结束或者有异常抛出
//通过读取两个int类型进行位操作得到.
public final long readLong() throws IOException {
return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
}
//从文件的当前文件指针读取一个float类型数据(float占用4个字节).所以一次性会读取4个字节
//在读取4个字节之前,方法会一直阻塞,除非流已经结束或者有异常抛出
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
//从文件的当前文件指针读取一个double类型数据(double占用8个字节).所以一次性会读取8个字节
//在读取8个字节之前,方法会一直阻塞,除非流已经结束或者有异常抛出
public final double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
//从文件中读取一行数据,包含从当前文件指针到换行符之间的数据.
//将每个字节转换成字符,只考虑低8位,高8位置为0.所以不支持全部的Unicode字符集
//一行结束以换行符(Unicode编码\u005Cn),回车符(Unicode编码是\u005Cr)为标识,回车符或者换行符不会包含在字符串中.
public final String readLine() throws IOException {
StringBuffer input = new StringBuffer();
int c = -1;
boolean eol = false;
while (!eol) {
switch (c = read()) {
//读取到文件末尾(-1),或者换行符('\n'),回车符('/r'),eol=true,将不再读取数据
case -1:
case '\n':
eol = true;
break;
case '\r':
eol = true;
long cur = getFilePointer();
if ((read()) != '\n') {
seek(cur);
}
break;
default:
input.append((char)c);
break;
}
}
//进入while循环,没有读取到数据,标记已经到达文件末尾.
if ((c == -1) && (input.length() == 0)) {
return null;
}
return input.toString();
}
//从文件中读取一个经过UTF-8编码解码的字符串
//读取的流程与写入方法WriterUTF()相反,前两个是字符串转经过编码转成字节长度,而不是字符串长度.
//根据前两个字节存储长度,读取对应字节,经过UTF-8解码,转换成字符串.
public final String readUTF() throws IOException {
return DataInputStream.readUTF(this);
}
//将一个布尔类型的数据写到文件中,从文件的当前文件指针位置开始
public final void writeBoolean(boolean v) throws IOException {
write(v ? 1 : 0);
//written++;
}
//将单个字节写到文件中,从文件的当前文件指针位置开始
public final void writeByte(int v) throws IOException {
write(v);
//written++;
}
//将一个short类型的数据写到文件中,从文件的当前文件指针位置开始
//高位优先写入.
public final void writeShort(int v) throws IOException {
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
//written += 2;
}
//将一个char类型的数据写到文件中,从文件的当前文件指针位置开始
//高位优先写入.
public final void writeChar(int v) throws IOException {
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
//written += 2;
}
//将一个int类型的数据写到文件中,从文件的当前文件指针位置开始.
//int占用4个字节,从高位到低位依次写到文件中.
public final void writeInt(int v) throws IOException {
write((v >>> 24) & 0xFF);
write((v >>> 16) & 0xFF);
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
//written += 4;
}
//将long类型数据写到文件中,从文件中当前文件指针位置开始.
//long共占用8个字节.分别是高位到低位一次写到文件中.
public final void writeLong(long v) throws IOException {
write((int)(v >>> 56) & 0xFF);
write((int)(v >>> 48) & 0xFF);
write((int)(v >>> 40) & 0xFF);
write((int)(v >>> 32) & 0xFF);
write((int)(v >>> 24) & 0xFF);
write((int)(v >>> 16) & 0xFF);
write((int)(v >>> 8) & 0xFF);
write((int)(v >>> 0) & 0xFF);
//written += 8;
}
//将一个float类型数据写到文件,从文件的当前文件指针位置开始(4个字节)
public final void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
//将一个double类型数据写到文件中,从文件的当前文件指针位置开始(8个字节)
public final void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
//将字符串中每个字符转成字节写到文件中,从文件的当前文件指针位置开始.
public final void writeChars(String s) throws IOException {
int clen = s.length();
int blen = 2*clen;
byte[] b = new byte[blen];
char[] c = new char[clen];
//获取字符串的所有的字符放到字符数组c中
s.getChars(0, clen, c, 0);
//一个字符占用两个字节,将每个字符对应字节写到字节数组b中
for (int i = 0, j = 0; i < clen; i++) {
b[j++] = (byte)(c[i] >>> 8);
b[j++] = (byte)(c[i] >>> 0);
}
writeBytes(b, 0, blen);
}
//将字符串str以编码UTF-8写到文件中,从文件中现在文件指针位置开始,前两个字节表示此次要写入的字节数
//而非字符串的长度.紧跟前两个字节写入的是字符串中每个字符以UTF-8编码的字节数据.
public final void writeUTF(String str) throws IOException {
DataOutputStream.writeUTF(str, this);
}
private static native void initIDs();
private native void close0() throws IOException;
static {
initIDs();
}
}