(最近刚来到简书平台,以前在CSDN上写的一些东西,也在逐渐的移到这儿来,有些篇幅是很早的时候写下的,因此可能会看到一些内容杂乱的文章,对此深感抱歉,以下为正文)
正文
本篇讲述的内容是Java IO中的RandomAccessFile。
RandomAccessFile类是Java IO体系中提供给我们的一种文件访问类,它自己同时包含了读写功能,当然它最大的特点是可以自由访问文件中的位置,无需从头开始访问,下面先贴上RandomAccessFile的源码,通过源码对其进行学习。
package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
public class RandomAccessFile implements DataOutput, DataInput, Closeable {
//声明了一个文件描述符的句柄。
private FileDescriptor fd;
//声明了一个文件通道的句柄,并将其指向null。
private FileChannel channel = null;
//定义了一个boolean型变量rw,用于表示文件的读写权限。
private boolean rw;
//定义了一个String类型的变量path,用于接收
private final String path;
//定义了一个Object对象,为后面close方法中的同步操作提供锁对象。
private Object closeLock = new Object();
//定义了一个boolean型变量closed,用于表示当前流是否关闭,用volatile关键字修饰,保证了其改变的可见性。
private volatile boolean closed = false;
//定义了4个常量,分别表示了RandomAccessFile的4种读写模式。
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; //同步的读写模式,具备读写模式的所有特性,当文件内容改变时,会立马同步写入到底层存储设备中。
/**
* 带两个参数的构造方法,第一个参数为String类型,表示需要进行操作的文件路径名,第二个参数为String类型,表示以什么模式打开文件(四种模式,r,rw,rws,
* rwd)。内部实质是继续调用后面的构造函数RandomAccessFile(File file, String mode)。
*/
public RandomAccessFile(String name, String mode)
throws FileNotFoundException
{
this(name != null ? new File(name) : null, mode);
}
/**
* 带两个参数的构造方法,第一个参数为File类型,表示要进行操作的文件对象,第二个参数为String类型,表示打开文件的模式。
*/
public RandomAccessFile(File file, String mode)
throws FileNotFoundException
{
// 定义了一个String类型变量name用于接收操作文件的路径名,如果文件为null,则name赋值为null。
String name = (file != null ? file.getPath() : null);
// 定义了一个int型变量imode,表示打开文件的模式状态,初始化默认为-1。
int imode = -1;
// 对传入的参数mode进行匹配,给imode,rw赋值,表示读写权限。
if (mode.equals("r"))
//"r",只读模式。
imode = O_RDONLY;
else if (mode.startsWith("rw")) {
//"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;
}
}
//如果imode<0(其实就是等于-1),那么抛出相应的异常,提示传入的读写模式是非法的,并告知是要填四种合法模式中的一种。
if (imode < 0)
throw new IllegalArgumentException("Illegal mode \"" + mode
+ "\" must be one of "
+ "\"r\", \"rw\", \"rws\","
+ " or \"rwd\"");
//获得java的安全管理器,根据rw的状态监测文件的读写权限。
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
if (rw) {
security.checkWrite(name);
}
}
//如果name为null,抛出相应的异常,NullPointerException。
if (name == null) {
throw new NullPointerException();
}
//如果file.isInvalid的值为true,则表示file对象不合法,抛出相应的异常,FileNotFoundException。
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
//将声明的句柄指向一个新建的文件描述符对象,并将本类对象依附在该文件描述符上。为path赋值,然后调用open方法根据指定路径和模式打开文件。
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;
}
}
/**
* 一个native方法,根据传入的文件路径及读写模式来打开文件。
*/
private native void open(String name, int mode)
throws FileNotFoundException;
//此处开始是RandomAccessFile的读操作
/**
* 定义了一个read方法,每次从文件中读取一个字节的内容,并以int型返回读取的数据。内部实质是调用native方法read0()。
*/
public int read() throws IOException {
return read0();
}
/**
* 定义了一个native方法read0,用于每次从文件中读取一个字节的内容,并以int型将读取的数据返回。
*/
private native int read0() throws IOException;
/**
* 定义了一个native方法readBytes方法,一次可以读取多个字节,并将读取的数据放入传入的字节数组当中,最终返回实际读取到的字节个数。
*/
private native int readBytes(byte b[], int off, int len) throws IOException;
/**
* 定义了一个带三个参数的read方法,第一个参数为一个byte型数组,用于存放读取的字节内容,第二和第三个参数都是一个int型数值,分别表示了读取的起点以及读
* 取的长度。内部实质上是调用native方法readBytes方法来读取数据。
*/
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}
/**
* 定义了一个带一个参数的read方法,传入的参数为一个byte型数组,用于存放读取的字节数据,内部实质上是调用native方法readBytes方法,起点为0长度为传入的
* 数组容量。
*/
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
/**
* 定义了一个readFully方法,每次读取多个字节数据,传入的参数为一个byte型数组。内部实质是调用之后的带三个参数的readFully方法,只有当数组存满或者文件
* 结尾或者抛出异常时才停止。
*/
public final void readFully(byte b[]) throws IOException {
readFully(b, 0, b.length);
}
/**
* 定义了一个带三个参数的readFully方法,第一个参数为一个byte型数组,用于存放读取的字节数据,第二和第三个参数为int型数据,分别为读取的起点和读取的长度
* 。当数组存满或者文件结尾或者抛出异常时才停止。
*/
public final void readFully(byte b[], int off, int len) throws IOException {
int n = 0;
// 通过一个循环,调用read方法来进行文件的读取,直到读取指定的长度。
do {
int count = this.read(b, off + n, len - n);
if (count < 0)
throw new EOFException();
n += count;
} while (n < len);
}
/**
* 定义了一个skipBytes方法,该方法用于跳过指定的字节数,传入的参数为一个int型数值,为需要跳过的字节数,最后返回实际跳过的字节数。
*/
public int skipBytes(int n) throws IOException {
//声明了3个long型变量,pos为当前读取的字节位置,len为文件的总长度,newpos表示跳过后指定字节后的读取读取位置。
long pos;
long len;
long newpos;
//如果传入的参数小于等于零,则返回零,表示没有跳过任何字节的数据。
if (n <= 0) {
return 0;
}
//通过getFilePointer方法,获取当前文件读取的索引位置。通过length方法获取文件的总长度。为newpos赋值,为当前位置加上跳过的字节数。
pos = getFilePointer();
len = length();
newpos = pos + n;
//如果newpos大于文件总长度,那么newpos置位到文件尾部。
if (newpos > len) {
newpos = len;
}
//调用seek方法,跳至newpos处。最终返回newpos-pos的值,表示实际跳过的字节数。
seek(newpos);
return (int) (newpos - pos);
}
//此处开始是RandomAccessFile的写操作。
/**
* 定义了一个write方法,每次写入一个字节的数据,传入的参数为一个int型值,即要写入的数据。内部实质是调用native方法write0来写入数据。
*/
public void write(int b) throws IOException {
write0(b);
}
//定义了一个native方法write0,传入的参数为int型数据,每次写入一个字节的数据。
private native void write0(int b) throws IOException;
/**
* 定义了一个native方法writeBytes,每次写入多个字节的数据,有三个参数,第一个参数为一个byte型数组,里面存放了要写入的数据,第二和第三个参数都是int型
* 数据,分别表示了写入的起点以及写入的长度。
*/
private native void writeBytes(byte b[], int off, int len) throws IOException;
/**
* 定义了一个write方法,每次写入多个字节的数据。传入的参数为一个byte型数组,里面存放了要写入的数据,内部实质调用了native方法writeBytes来写入数据。
*/
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length);
}
/**
* 定义了一个带3个参数的write方法,每次写入多个字节的数据。第一个参数为一个byte型数组,里面存放了要写入的字节数据,第二和第三个参数为int型数据,分别
* 表示了写入的起点以及写入的长度。内部实质是调用native方法writeBytes来写入数据。
*/
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len);
}
//RandomAccessFile独有的随机读取操作。
/**
* 定义了一个native方法getFilePointer,该方法用来获取当前文件读取的位置。
*/
public native long getFilePointer() throws IOException;
/**
* 定义了一个seek方法,用于跳过指定字节长度的数据。传入的参数为一个long型数据,代表着要跳过的字节数。
*/
public void seek(long pos) throws IOException {
//对传入的参数进行安全监测,如果其小于零,则抛出相应的异常。
if (pos < 0) {
throw new IOException("Negative seek offset");
} else {
//调用native方法seek0,用来跳过指定字节数量的数据。
seek0(pos);
}
}
//定义了一个native方法seek0,用于跳过指定字节长度的数据,传入的参数为一个long型数据,其表示要跳过的字节长度。
private native void seek0(long pos) throws IOException;
/**
* 定义了一个native方法length,用于获得文件的总长度
*/
public native long length() throws IOException;
/**
* 定义了一个native方法setLength,用于重新设定文件的长度。
*/
public native void setLength(long newLength) throws IOException;
/**
* 定义了一个close方法,用于关闭流及其相关联的系统资源。
*/
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();
}
});
}
//一些从DataInputStream/DataOuputStream中读写方法,方便对不同类型的数据进行读写。
/**
* 读取一个boolean型的数据,工作原理其实就是读取一个字节的数据,比较其是否等于0,因为0表示false,非0表示true。
*/
public final boolean readBoolean() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return (ch != 0);
}
/**
* 读取一个字节的数据,工作原理其实就是调用read方法读取一个字节的数据,然后转换成byte型数据。
*/
public final byte readByte() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return (byte)(ch);
}
/**
* 读取一个不带符号字节数,工作原理就是直接调用read方法并返回。
*/
public final int readUnsignedByte() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return ch;
}
/**
* 读取一个short型数据,工作原理其实就是调用两次read方法,读取的数据作为short型数据的高八位和低八位,然后返回该数据。
*/
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型数据,工作原理其实就是调用两次read方法,读取的数据作为short型数据的高八位和低八位,然后返回改数据。
*/
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);
}
/**
* 读取一个字符,工作原理就是调用两次read方法,读取的数据作为char型数据的高八位和低八位,然后将其返回。
*/
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型数据,调用4次read方法,读取的数据代表着int型数据高位到地位,最终返回一个int型数据
*/
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型数据,本质是调用了两次readInt方法,读取的数据作为long型数据的高32位和低32位。
*/
public final long readLong() throws IOException {
return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
}
/**
* 读取一个Float型数据,原理是通过readInt读取一个int型数据,然后将其转换为Float型数据并返回。
*/
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
/**
* 读取一个Double型数据,原理是通过readLong读取一个lo ng型数据,然后将其转换为Double型数据并返回。
*/
public final double readDouble() throws IOException {
return Double.long BitsToDouble(readLong());
}
/**
* 一次读取一行数据,并将读取到的数据返回。
*/
public final String readLine() throws IOException {
//创建了一个StringBuffer对象,用于接收读取的数据。
StringBuffer input = new StringBuffer();
//声明了一个int型变量c,用于接收读取的数据,声明了一个boolean型变量eol,表明是否读取到了换行符。
int c = -1;
boolean eol = false;
//通过一个循环来不断读取数据。
while (!eol) {
switch (c = read()) {
case -1:
//返回-1表示文件已经读取完毕。
case '\n':
//返回'\n',表示读到换行符,此时将eol置为true,跳出循环。
eol = true;
break;
case '\r':
//返回'\r',将eol置为true,因为平台换行符不同,向后读取看是否有'\n',如果没有则返回'\r'处,然后跳出循环
eol = true;
long cur = getFilePointer();
if ((read()) != '\n') {
seek(cur);
}
break;
default:
//每次操作向input中添加读取的内容。
input.append((char)c);
break;
}
}
//如果没有读取到任何数据,则返回null。最终将input转化成String类型然后返回。
if ((c == -1) && (input.length() == 0)) {
return null;
}
return input.toString();
}
/**
* 定义了一个readUTF的方法,实质是直接调用DataInputStream的readUTF方法。
*/
public final String readUTF() throws IOException {
return DataInputStream.readUTF(this);
}
/**
* 定义了一个写入Bollean型数据的方法。原理是根据数据是否等于0来决定true和false。
*/
public final void writeBoolean(boolean v) throws IOException {
write(v ? 1 : 0);
}
/**
* 每次写入一个字节的数据。
*/
public final void writeByte(int v) throws IOException {
write(v);
}
/**
* 每次写入一个short型数据。实际上是通过两次write方法,每次写入一个字节的数据,分别写入short型数据高八位和低八位。
*/
public final void writeShort(int v) throws IOException {
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
}
/**
* 每次写入一个char型数据。实际上是通过两次write方法,每次写入一个字节的数据,分别写入char型数据高八位和低八位。
*/
public final void writeChar(int v) throws IOException {
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
}
/**
* 每次写入一个int型数据。实际上是通过4次write方法,每次写入一个字节的数据,从最高位开始,每八位一组依次写入。
*/
public final void writeInt(int v) throws IOException {
write((v >>> 24) & 0xFF);
write((v >>> 16) & 0xFF);
write((v >>> 8) & 0xFF);
write((v >>> 0) & 0xFF);
}
/**
* 每次写入一个long型数据。实际上是通过8次write方法,每次写入一个字节的数据,从最高位开始,每八位一组依次写入。
*/
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);
}
/**
* 每次写入一个Float型数据。实际上是先将float型数据装换成int型数据,然后调用writeInt方法写入数据。
*/
public final void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
/**
* 每次写入一个Double型数据。实际上是先将double型数据转换成long型数据,然后调用writeLong方法写入数据。
*/
public final void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
/**
* 每次写入一个字符串,实质上是先将字符串转换为字节数组,然后调用writeBytes方法写入。
*/
@SuppressWarnings("deprecation")
public final void writeBytes(String s) throws IOException {
int len = s.length();
byte[] b = new byte[len];
s.getBytes(0, len, b, 0);
writeBytes(b, 0, len);
}
/**
* 每次写入一个字符串,实质上是先将字符串转换成字符数组,在将字符数组转化成字节数组,然后调用writeBytes方法写入。
*/
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];
s.getChars(0, clen, c, 0);
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);
}
/**
* 每次读取一个UTF字符串,实质上是直接调用DataOutputStream的writeUTF方法。
*/
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();
}
}
通过以上对源码的简单分析,我们对RandomAccessFile类有了初步的认识,它自身就包含了读写功能,同时具有随机读取这一大亮点。下面用一个简单的例子来简单展示一下它的用法。
package RandomIO;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomIOTest1 {
public static void main(String[] args) {
final File source = new File("./src/file/test.avi");
final File target = new File("./src/file/testcopy.avi");
int ThreadNum = (int) Math
.ceil(Math.ceil((double) source.length() / 1024 / 1024 / 10));
for (int i = 0; i < ThreadNum; i++) {
Thread thread = new Thread(new MyRunnable(i, source, target));
thread.start();
}
}
}
class MyRunnable implements Runnable {
private int num;
private File source;
private File target;
MyRunnable(int num, File source, File target) {
this.num = num;
this.source = source;
this.target = target;
}
@Override
public void run() {
try (RandomAccessFile sourceFile = new RandomAccessFile(source, "rw");
RandomAccessFile targetFile = new RandomAccessFile(target, "rw");) {
System.out.println("线程" + num + "启动");
sourceFile.seek(num * 1024 * 1024 * 10);
targetFile.seek(num * 1024 * 1024 * 10);
byte[] buffer = null;
if ((sourceFile.length() - sourceFile.getFilePointer()) < 1024 * 1024 * 10) {
buffer = new byte[(int) (sourceFile.length() - sourceFile
.getFilePointer())];
} else {
buffer = new byte[1024 * 1024 * 10];
}
sourceFile.read(buffer);
targetFile.write(buffer);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("线程" + num + "复制结束");
}
}
该例子使用了多线程同时复制一个文件,每个线程拷贝10MB的数据,运行上述代码后可以在指定路径下看到拷贝成功的文件,笔者拷贝了一个100多MB的小视频,拷贝的视频可以正常播放,效果图如下:
运行效果
RandomAccessFile类的功能十分强大,但人无完人,它也有着一定的缺陷,从源码中可以看出,RandomAccessFile类在进行读写操作时,都是直接与底层介质进行数据传递的,即使是读写一个字节的数据,也必须进行一次I/O操作,这样就大大降低了其工作的效率。在前面如BufferedReader/BufferedWriter的学习中,我们了解了我们可以通过内置一个数据缓存区来提升读写效率,RandomAccessFile也同样可以这样操作,我们可以完全重构一个属于自己的带缓存的BufferedRandomAccessFile类。
package RandomIO;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
public class BufferedRandomAccessFile1 extends RandomAccessFile {
private static final int Default_Buffer_Size = 1024 * 8;
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
private static final long BuffMask_ = ~(((long) Default_Buffer_Size) - 1L);
//表示缓存区是否有未flush的数据。
private boolean hasDatas;
//表示是否进行同步操作,将缓存内容flush。
private boolean syncNeeded_;
//当前操作文件的索引位置(包括在缓存区中)。
private long cPos = 0L;
//磁盘上操作文件的索引位置(存储介质中)。
private long diskPos_ = 0L;
private long lo_, hi_ = 0L;
private long maxHi_ = (long)Default_Buffer_Size;
//是否到了文件结束部分。
private boolean isEOF;
//内置的一个数组缓存区,默认大小是8k。
private byte[] buffer;
public BufferedRandomAccessFile1(File file, String mode) throws IOException {
this(file, mode, Default_Buffer_Size);
}
public BufferedRandomAccessFile1(String name, String mode)
throws IOException {
this(name, mode, Default_Buffer_Size);
}
public BufferedRandomAccessFile1(File file, String mode, int size)
throws IOException {
super(file, mode);
init(size);
}
public BufferedRandomAccessFile1(String name, String mode, int size)
throws FileNotFoundException {
super(name, mode);
init(size);
}
//对内置缓存区进行初始化
private void init(int size) {
if (size < Default_Buffer_Size) {
size = Default_Buffer_Size;
} else if (size > MAX_BUFFER_SIZE) {
size = MAX_BUFFER_SIZE;
}
buffer = new byte[size];
}
//将缓存区中的数据同步写出到存储介质中。
public void sync() throws IOException {
if (syncNeeded_) {
//将内置缓存区中的数据写入
flush();
//将文件通道内未写入磁盘的数据强制写入到磁盘中,传入的参数表示是否将文件元信息写入到磁盘之上。
getChannel().force(true);
syncNeeded_ = false;
}
}
// close前将缓存区刷新一次防止缓存区中有未写入的数据,然后将缓存区置为null,调用父类的close方法释放资源。
public void close() throws IOException {
this.flush();
this.buffer = null;
super.close();
}
//将缓存区中内容写入存储介质中
public void flush() throws IOException {
this.flushBuffer();
}
//将缓存中内容写入存储介质之中
private void flushBuffer() throws IOException {
if (hasDatas) {
if (diskPos_ != lo_)
super.seek(lo_);
int len = (int) (cPos - lo_);
super.write(buffer, 0, len);
diskPos_ = cPos;
hasDatas = false;
}
}
//向缓存区中填充数据。返回实际填充了多少字节的数据。
private int fillBuffer() throws IOException {
int nextChar = 0;
int nChars = buffer.length;
//通过一个循环,向缓存区中填充数据,直至将缓存区填满或者文件读到末尾。
while (nChars > 0) {
int n = super.read(buffer, nextChar, nChars);
if (n < 0)
break;
nextChar += n;
nChars -= n;
}
if ((nextChar < 0) && (isEOF = (nextChar < buffer.length))) {
//将为缓存区中未填充到的部分全用-1初始化。
Arrays.fill(buffer, nextChar, buffer.length, (byte) 0xff);
}
diskPos_ += nextChar;
return nextChar;
}
//跳过指定的字节数
public void seek(long pos) throws IOException {
if (pos >= hi_ || pos < lo_) {
flushBuffer();
lo_ = pos & BuffMask_;
maxHi_ = lo_ + (long) buffer.length;
if (diskPos_ != lo_) {
super.seek(lo_);
diskPos_ = lo_;
}
int n = fillBuffer();
hi_ = lo_ + (long) n;
} else {
if (pos < cPos) {
flushBuffer();
}
}
cPos = pos;
}
public long getFilePointer() {
return cPos;
}
public long length() throws IOException {
return Math.max(cPos, super.length());
}
public int read() throws IOException {
if (cPos >= hi_) {
if (isEOF)
return -1;
seek(cPos);
if (cPos == hi_)
return -1;
}
byte res = buffer[(int) (cPos - lo_)];
cPos++;
return ((int) res) & 0xFF;
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public int read(byte[] b, int off, int len) throws IOException {
if (cPos >= hi_) {
if (isEOF)
return -1;
seek(cPos);
if (cPos == hi_)
return -1;
}
len = Math.min(len, (int) (hi_ - cPos));
int buffOff = (int) (cPos - lo_);
System.arraycopy(buffer, buffOff, b, off, len);
cPos += len;
return len;
}
public void write(int b) throws IOException {
if (cPos >= hi_) {
if (isEOF && hi_ < maxHi_) {
hi_++;
} else {
seek(cPos);
if (cPos == hi_) {
hi_++;
}
}
}
buffer[(int) (cPos - lo_)] = (byte) b;
cPos++;
hasDatas = true;
syncNeeded_ = true;
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
while (len > 0) {
int n = writeAtMost(b, off, len);
off += n;
len -= n;
hasDatas = true;
syncNeeded_ = true;
}
}
private int writeAtMost(byte[] b, int off, int len) throws IOException {
if (cPos >= hi_) {
if (isEOF && hi_ < maxHi_) {
hi_ = maxHi_;
} else {
seek(cPos);
if (cPos == hi_) {
hi_ = maxHi_;
}
}
}
len = Math.min(len, (int) (hi_ - cPos));
int buffOff = (int) (cPos - lo_);
System.arraycopy(b, off, buffer, buffOff, len);
cPos += len;
return len;
}
}
最终,用一个小例子来验证其工作效率,我们将比较RandomAccessFile,BufferedRandomAccessFile,BufferedInput/OutputStream的效率。
package RandomIO;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
public class RandomIOTest2 {
public static void main(String[] args) {
long startTime;
long endTime;
File source = new File("./src/file/test.avi");
File target = new File("./src/file/testcopy.avi");
byte[] buffer = new byte[1024];
startTime = System.currentTimeMillis();
int len;
try (RandomAccessFile sourceFile = new RandomAccessFile(source, "rw");
RandomAccessFile targetFile = new RandomAccessFile(target, "rw")) {
while ((len = sourceFile.read(buffer)) != -1) {
targetFile.write(buffer, 0, len);
}
endTime = System.currentTimeMillis();
System.out.println("RandomAccessFile拷贝耗时" + (endTime - startTime)
+ "ms");
} catch (Exception e) {
}
startTime = System.currentTimeMillis();
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(source));
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(target));) {
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
endTime = System.currentTimeMillis();
System.out.println("BufferedInputStream/BuffedOutputStream拷贝耗时"
+ (endTime - startTime) + "ms");
} catch (Exception e) {
}
startTime = System.currentTimeMillis();
try (BufferedRandomAccessFile1 sourceFile = new BufferedRandomAccessFile1(
source, "rw");
BufferedRandomAccessFile1 targetFile = new BufferedRandomAccessFile1(
target, "rw")) {
while ((len = sourceFile.read(buffer)) != -1) {
targetFile.write(buffer, 0, len);
}
endTime = System.currentTimeMillis();
System.out.println("BufferedRandomAccessFile1拷贝耗时" + (endTime - startTime)
+ "ms");
} catch (Exception e) {
}
}
}
执行上述代码,可以看到以下输出结果:
控制台输出
从输出中可以看出,RandomAccessFile的效率确实很低,但加上缓存后,工作效率立马提升。
以上为本篇的全部内容。
网友评论