java IO笔记(RandomAccessFile)

本篇讲述的内容是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的效率确实很低,但加上缓存后,工作效率立马提升。
以上为本篇内容。

 

posted @ 2017-10-10 22:06  moonfish  阅读(515)  评论(0编辑  收藏  举报