• java.io.RandomAccessFile


    在正式介绍如何使用Java的输入/输出相关类来进行文件存取前,先简单地通过使用java.io.RandomAccessFile来存取文件,以认识一些文件存取时所必须注意的概念与事项。

    文件存取通常是循序的,每在文件中存取一次,文件的读取位置就会相对于目前的位置前进一次。然而有时必须指定文件的某个区段进行读取或写入的动作,也就是进行随机存取(Random Access),即要能在文件中随意地移动读取位置。这时可以使用RandomAccessFile,使用它的seek()方法来指定文件存取的位置,指定的单位是字节。

    为了移动存取位置时的方便,通常在随机存取文件中会固定每一个数据的长度。例如长度固定为每一个学生个人数据,Java中并没有直接的方法可以写入一个固定长度数据(C/C++中的structure),所以在固定每一个长度方面必须自行设计。范例14.2先设计一个学生数据的类。

    范例14.2  Student.java

    package onlyfun.caterpillar;

    public class Student {
    private String name;
    private int score;

    public Student() {
    setName("noname");
    }

    public Student(String name, int score) {
    setName(name);
    this.score = score;
    }

    public void setName(String name) {
    StringBuilder builder = null;
    if(name != null)
    builder = new StringBuilder(name);
    else
    builder = new StringBuilder(15);

    builder.setLength(15); //
    最长 15 字符
    this.name = builder.toString();
    }

    public void setScore(int score) {
    this.score = score;
    }

    public String getName() {
    return name;
    }

    public int getScore() {
    return score;
    }
    //
    每个数据固定写入34字节
    public static int size() {
    return 34;
    }
    }

    对于每一个学生数据的实例在写入文件时,会固定以34字节的长度写入,也就是15个字符(30字节)加上一个int整数的长度(4字节)。范例14.2中是使用StringBuilder来固定字符长度,可以使用size()方法来取得长度信息。范例14.3则示范了如何使用RandomAccessFile来写入文件,并可随机指定一个所想读出的数据。

    范例14.3  RandomAccessFileDemo.java

    package onlyfun.caterpillar;

    import java.io.*;
    import java.util.*;

    public class RandomAccessFileDemo {
    public static void main(String[] args) {
    Student[] students = {
    new Student("Justin", 90),
    new Student("momor", 95),
    new Student("Bush", 88),
    new Student("caterpillar", 84)};

    try {
    File file = new File(args[0]);
    //
    建立RandomAccessFile实例并以读写模式打开文件
    RandomAccessFile randomAccessFile =
    new RandomAccessFile(file, "rw");

    for(int i = 0; i < students.length; i++) {
    //
    使用对应的write方法写入数据
    randomAccessFile.writeChars(students[i].getName());
    randomAccessFile.writeInt(students[i].getScore());
    }

    Scanner scanner = new Scanner(System.in);

    System.out.print("
    读取第几个数据?");

    int num = scanner.nextInt();

    //
    使用seek()方法操作存取位置
    randomAccessFile.seek((num-1) * Student.size());
    Student student = new Student();

    // 使用对应的read方法读出数据
    student.setName(readName(randomAccessFile));
    student.setScore(randomAccessFile.readInt());

    System.out.println("姓名:" + student.getName());
    System.out.println("
    分数:" + student.getScore());

    //
    设置关闭文件
    randomAccessFile.close();
    }
    catch(ArrayIndexOutOfBoundsException e) {
    System.out.println("
    请指定文件名称");
    }
    catch(IOException e) {
    e.printStackTrace();
    }
    }

    private static String readName(RandomAccessFile randomAccessfile)
    throws IOException {
    char[] name = new char[15];

    for(int i = 0; i < name.length; i++)
    name[i] = randomAccessfile.readChar();

    // 将空字符取代为空格符并返回
    return new String(name).replace('\0', ' ');
    }
    }

    执行结果:

    java onlyfun.caterpillar.RandomAccessFileDemo student.dat
    读取第几个数据?2
    姓名:momor
    分数:95

    RandomAccessFile上的相关方法实现都在批注中说明了,可以看到读写文件时几个必要的流程:

    打开文件并指定读写方式

    Java中,当实例化一个与文件相关的输入/输出类时,就会进行打开文件的动作。在实例化的同时要指定文件是要以读出(r)、写入(w)或可读可写(rw)的方式打开,可以将文件看作是一个容器,要读出或写入数据都必须打开容器的瓶盖。

    使用对应的写入方法

    对文件进行写入,要使用对应的写入方法。在Java中通常是write的名称作为开头,在低级的文件写入中,要写入某种类型的数据,就要使用对应该类型的方法,如writeInt()writeChar()等。

    使用对应的读出方法

    对文件进行读出,要使用对应的读出方法。在Java中通常是read的名称作为开头,在低级的文件读出中,要读出某种类型的数据,就要使用对应该类型的方法,如readInt()readChar()等。

    关闭文件

    可以将文件看作是一个容器,要读出或写入数据都必须打开容器的瓶盖,而不进行读出或写入时,就要将瓶盖关闭。对于某些文件存取对象来说,关闭文件的动作意味着将缓冲区(Buffer)的数据全部写入文件,如果不作关闭文件的动作,某些数据可能没有写入文件而遗失。

    ==========================================================================================================================================

    RandomAccessFile是一个很有用的类,可以将字节流写入到磁盘文件中,对应的也可以从磁盘文件中读取出字节流,在API中关于RandomAccessFile的描述如下:

    此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针 ;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。

    通常,如果此类中的所有读取例程在读取所需数量的字节之前已到达文件末尾,则抛出 EOFException (是一种 IOException )。如果由于某些原因无法读取任何字节,而不是在读取所需数量的字节之前已到达文件末尾,则抛出 IOException ,而不是 EOFException 。需要特别指出的是,如果流已被关闭,则可能抛出 IOException

    以下是两个RandomAccessFile的写入和读取的简单例子:

    1、 将字节流写入到磁盘中

    Java代码 复制代码 收藏代码
    1. private static void testCreateFile(){
    2. String directory = “D:/program/test”;
    3. String name = “t.gen”;
    4. File f = new File(directory, name);
    5. RandomAccessFile file = null;
    6. try {
    7. file = new RandomAccessFile(f, “rw”);
    8. byte[] b = {5,10,15,20};
    9. try {
    10. //如果没有这行,文件也会生成,只是文件为空
    11. file.write(b,0,4);
    12. } catch (IOException e) {
    13. e.printStackTrace();
    14. }
    15. } catch (FileNotFoundException e) {
    16. e.printStackTrace();
    17. }finally{
    18. if (null!=file){
    19. try {
    20. file.close();
    21. } catch (IOException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. }
    26. }
    private static void testCreateFile(){
    
    String directory = “D:/program/test”;
    
    String name = “t.gen”;
    
    File f = new File(directory, name);
    
    RandomAccessFile file = null;
    
    try {
    
    file = new RandomAccessFile(f, “rw”);
    
    byte[] b = {5,10,15,20};
    
    try {
    
    //如果没有这行,文件也会生成,只是文件为空
    
    file.write(b,0,4);
    
    } catch (IOException e) {
    
    e.printStackTrace();
    
    }
    
    } catch (FileNotFoundException e) {
    
    e.printStackTrace();
    
    }finally{
    
    if (null!=file){
    
    try {
    
    file.close();
    
    } catch (IOException e) {
    
    e.printStackTrace();
    
    }
    
    }
    
    }
    
    }
    Java代码 复制代码 收藏代码
    1. 2、 从磁盘文件中读取字节流
    2. private static void testReadFile(){
    3. String directory = “D:/program/luceneDemo3.0/test”;
    4. String name = “t.gen”;
    5. File f = new File(directory, name);
    6. RandomAccessFile file = null;
    7. try {
    8. file = new RandomAccessFile(f, “rw”);
    9. byte[] b = new byte[4];
    10. try {
    11. long len = file.length();
    12. file.read(b);
    13. //设置要读取的字节位置
    14. file.seek(1);
    15. System.out.println(file.readByte()+”>>FilePointer>>”+file.getFilePointer());
    16. for (int i=0;i<b.length;i++){
    17. System.out.println(“>>>”+b[i]);
    18. }
    19. } catch (IOException e) {
    20. e.printStackTrace();
    21. }
    22. } catch (FileNotFoundException e) {
    23. e.printStackTrace();
    24. }finally{
    25. if (null!=file){
    26. try {
    27. file.close();
    28. } catch (IOException e) {
    29. e.printStackTrace();
    30. }
    31. }
    32. }
    33. }
    2、  从磁盘文件中读取字节流
    
    private static void testReadFile(){
    
    String directory = “D:/program/luceneDemo3.0/test”;
    
    String name = “t.gen”;
    
    File f = new File(directory, name);
    
    RandomAccessFile file = null;
    
    try {
    
    file = new RandomAccessFile(f, “rw”);
    
    byte[] b = new byte[4];
    
    try {
    
    long len = file.length();
    
    file.read(b);
    
    //设置要读取的字节位置
    
    file.seek(1);
    
    System.out.println(file.readByte()+”>>FilePointer>>”+file.getFilePointer());
    
    for (int i=0;i<b.length;i++){
    
    System.out.println(“>>>”+b[i]);
    
    }
    
    } catch (IOException e) {
    
    e.printStackTrace();
    
    }
    
    } catch (FileNotFoundException e) {
    
    e.printStackTrace();
    
    }finally{
    
    if (null!=file){
    
    try {
    
    file.close();
    
    } catch (IOException e) {
    
    e.printStackTrace();
    
    }
    
    }
    
    }
    
    }

    说明

    1、 这个类依我看来,用来处理字节流(byte)是很好的,如果用来处理字符(串)或其他数据类型,比如int、long,我试了感觉效果并不好,尤其是处理中文字符串的时候,那简直就是一个灾难,你会又碰上纠缠不清的乱码!

    2、 seek(long pos)方法

    是在读取的时候用来设置读取到哪一个字节的,比如在例子中有5,10,15,20字节,在byte数组中分别对应0、1、2、3位置,同样在文件 file = new RandomAccessFile(f, “rw”);中,也对应着0、1、2、3位置,所以如果设置file.seek(1);就表示通过file.readByte()读取的时候,读取的是第 1位置的数据,也就是10了。

    3、 getFilePointer()方法

    在通过上面说的seek (long pos)设置后,getFilePointer()得到的就是当前文件中的字节位置,也就是所说的偏移量了。比如在这个例子中,getFilePointer()的值就是1.

    4、文件模式

    “r” 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。

    “rw” 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。

    “rws” 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。

    “rwd” 打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。

    附录

    void close ()
    关闭此随机访问文件流并释放与该流关联的所有系统资源。
    FileChannel getChannel ()
    返回与此文件关联的唯一 FileChannel 对象。
    FileDescriptor getFD ()
    返回与此流关联的不透明文件描述符对象。
    long getFilePointer ()
    返回此文件中的当前偏移量。
    long length ()
    返回此文件的长度。
    int read ()
    从此文件中读取一个数据字节。
    int read (byte[] b)
    将最多 b.length 个数据字节从此文件读入 byte 数组。
    int read (byte[] b, int off, int len)
    将最多 len 个数据字节从此文件读入 byte 数组。
    boolean readBoolean ()
    从此文件读取一个 boolean。
    byte readByte ()
    从此文件读取一个有符号的八位值。
    char readChar ()
    从此文件读取一个字符。
    double readDouble ()
    从此文件读取一个 double。
    float readFloat ()
    从此文件读取一个 float。
    void readFully (byte[] b)
    将 b.length 个字节从此文件读入 byte 数组,并从当前文件指针开始。
    void readFully (byte[] b, int off, int len)
    将正好 len 个字节从此文件读入 byte 数组,并从当前文件指针开始。
    int readInt ()
    从此文件读取一个有符号的 32 位整数。
    String readLine ()
    从此文件读取文本的下一行。
    long readLong ()
    从此文件读取一个有符号的 64 位整数。
    short readShort ()
    从此文件读取一个有符号的 16 位数。
    int readUnsignedByte ()
    从此文件读取一个无符号的八位数。
    int readUnsignedShort ()
    从此文件读取一个无符号的 16 位数。
    String readUTF ()
    从此文件读取一个字符串。
    void seek (long pos)
    设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
    void setLength (long newLength)
    设置此文件的长度。
    int skipBytes (int n)
    尝试跳过输入的 n 个字节以丢弃跳过的字节。
    void write (byte[] b)
    将 b.length 个字节从指定 byte 数组写入到此文件,并从当前文件指针开始。
    void write (byte[] b, int off, int len)
    将 len 个字节从指定 byte 数组写入到此文件,并从偏移量 off 处开始。
    void write (int b)
    向此文件写入指定的字节。
    void writeBoolean (boolean v)
    按单字节值将 boolean 写入该文件。
    void writeByte (int v)
    按单字节值将 byte 写入该文件。
    void writeBytes (String s)
    按字节序列将该字符串写入该文件。
    void writeChar (int v)
    按双字节值将 char 写入该文件,先写高字节。
    void writeChars (String s)
    按字符序列将一个字符串写入该文件。
    void writeDouble (double v)
    使用 Double 类中的 doubleToLongBits 方法将双精度参数转换为一个 long,然后按八字节数量将该 long 值写入该文件,先定高字节。
    void writeFloat (float v)
    使用 Float 类中的 floatToIntBits 方法将浮点参数转换为一个 int,然后按四字节数量将该 int 值写入该文件,先写高字节。
    void writeInt (int v)
    按四个字节将 int 写入该文件,先写高字节。
    void writeLong (long v)
    按八个字节将 long 写入该文件,先写高字节。
    void writeShort (int v)
    按两个字节将 short 写入该文件,先写高字节。
    void writeUTF (String str)
    使用 modified UTF-8 编码以与机器无关的方式将一个字符串写入该文件。

    -------------------------------------------------------------------------------------------------------------------------------------------

    RandomAccessFile类。其I/O性能较之其它常用开发语言的同类性能差距甚远,严重影响程序的运行效率。

    开发人员迫切需要提高效率,下面分析RandomAccessFile等文件类的源代码,找出其中的症结所在,并加以改进优化,创建一个"性/价比"俱佳的随机文件访问类BufferedRandomAccessFile。

    在改进之前先做一个基本测试:逐字节COPY一个12兆的文件(这里牵涉到读和写)。

    耗用时间(秒)
    RandomAccessFile RandomAccessFile 95.848
    BufferedInputStream + DataInputStream BufferedOutputStream + DataOutputStream 2.935

    我们可以看到两者差距约32倍,RandomAccessFile也太慢了。先看看两者关键部分的源代码,对比分析,找出原因。

    1.1.[RandomAccessFile]

    Java代码 复制代码 收藏代码
    1. public class RandomAccessFile implements DataOutput, DataInput {
    2. public final byte readByte() throws IOException {
    3. int ch = this.read();
    4. if (ch < 0)
    5. throw new EOFException();
    6. return (byte)(ch);
    7. }
    8. public native int read() throws IOException;
    9. public final void writeByte(int v) throws IOException {
    10. write(v);
    11. }
    12. public native void write(int b) throws IOException;
    13. }

    public class RandomAccessFile implements DataOutput, DataInput {
    	public final byte readByte() throws IOException {
    		int ch = this.read();
    		if (ch < 0)
    			throw new EOFException();
    		return (byte)(ch);
    	}
    	public native int read() throws IOException; 
    	public final void writeByte(int v) throws IOException {
    		write(v);
    	} 
    	public native void write(int b) throws IOException; 
    }

    可见,RandomAccessFile每读/写一个字节就需对磁盘进行一次I/O操作。

    1.2.[BufferedInputStream]

    Java代码 复制代码 收藏代码
    1. public class BufferedInputStream extends FilterInputStream {
    2. private static int defaultBufferSize = 2048;
    3. protected byte buf[]; // 建立读缓存区
    4. public BufferedInputStream(InputStream in, int size) {
    5. super(in);
    6. if (size <= 0) {
    7. throw new IllegalArgumentException("Buffer size <= 0");
    8. }
    9. buf = new byte[size];
    10. }
    11. public synchronized int read() throws IOException {
    12. ensureOpen();
    13. if (pos >= count) {
    14. fill();
    15. if (pos >= count)
    16. return -1;
    17. }
    18. return buf[pos++] & 0xff; // 直接从BUF[]中读取
    19. }
    20. private void fill() throws IOException {
    21. if (markpos < 0)
    22. pos = 0; /* no mark: throw away the buffer */
    23. else if (pos >= buf.length) /* no room left in buffer */
    24. if (markpos > 0) { /* can throw away early part of the buffer */
    25. int sz = pos - markpos;
    26. System.arraycopy(buf, markpos, buf, 0, sz);
    27. pos = sz;
    28. markpos = 0;
    29. } else if (buf.length >= marklimit) {
    30. markpos = -1; /* buffer got too big, invalidate mark */
    31. pos = 0; /* drop buffer contents */
    32. } else { /* grow buffer */
    33. int nsz = pos * 2;
    34. if (nsz > marklimit)
    35. nsz = marklimit;
    36. byte nbuf[] = new byte[nsz];
    37. System.arraycopy(buf, 0, nbuf, 0, pos);
    38. buf = nbuf;
    39. }
    40. count = pos;
    41. int n = in.read(buf, pos, buf.length - pos);
    42. if (n > 0)
    43. count = n + pos;
    44. }
    45. }

    public class BufferedInputStream extends FilterInputStream {
    	private static int defaultBufferSize = 2048; 
    	protected byte buf[]; // 建立读缓存区
    	public BufferedInputStream(InputStream in, int size) {
    		super(in);        
    		if (size <= 0) {
    			throw new IllegalArgumentException("Buffer size <= 0");
    		}
    		buf = new byte[size];
    	}
    	public synchronized int read() throws IOException {
    		ensureOpen();
    		if (pos >= count) {
    			fill();
    			if (pos >= count)
    				return -1;
    		}
    		return buf[pos++] & 0xff; // 直接从BUF[]中读取
    	} 
    	private void fill() throws IOException {
    	if (markpos < 0)
    	    pos = 0;		/* no mark: throw away the buffer */
    	else if (pos >= buf.length)	/* no room left in buffer */
    	    if (markpos > 0) {	/* can throw away early part of the buffer */
    		int sz = pos - markpos;
    		System.arraycopy(buf, markpos, buf, 0, sz);
    		pos = sz;
    		markpos = 0;
    	    } else if (buf.length >= marklimit) {
    		markpos = -1;	/* buffer got too big, invalidate mark */
    		pos = 0;	/* drop buffer contents */
    	    } else {		/* grow buffer */
    		int nsz = pos * 2;
    		if (nsz > marklimit)
    		    nsz = marklimit;
    		byte nbuf[] = new byte[nsz];
    		System.arraycopy(buf, 0, nbuf, 0, pos);
    		buf = nbuf;
    	    }
    	count = pos;
    	int n = in.read(buf, pos, buf.length - pos);
    	if (n > 0)
    	    count = n + pos;
    	}
    }

    1.3.[BufferedOutputStream]

    Java代码 复制代码 收藏代码
    1. public class BufferedOutputStream extends FilterOutputStream {
    2. protected byte buf[]; // 建立写缓存区
    3. public BufferedOutputStream(OutputStream out, int size) {
    4. super(out);
    5. if (size <= 0) {
    6. throw new IllegalArgumentException("Buffer size <= 0");
    7. }
    8. buf = new byte[size];
    9. }
    10. public synchronized void write(int b) throws IOException {
    11. if (count >= buf.length) {
    12. flushBuffer();
    13. }
    14. buf[count++] = (byte)b; // 直接从BUF[]中读取
    15. }
    16. private void flushBuffer() throws IOException {
    17. if (count > 0) {
    18. out.write(buf, 0, count);
    19. count = 0;
    20. }
    21. }
    22. }

    public class BufferedOutputStream extends FilterOutputStream {
       protected byte buf[]; // 建立写缓存区
       public BufferedOutputStream(OutputStream out, int size) {
    		super(out);
    		if (size <= 0) {
    			throw new IllegalArgumentException("Buffer size <= 0");
    		}
    		buf = new byte[size];
        } 
    public synchronized void write(int b) throws IOException {
    		if (count >= buf.length) {
    	   		flushBuffer();
    		}
    		buf[count++] = (byte)b; // 直接从BUF[]中读取
       }
       private void flushBuffer() throws IOException {
    		if (count > 0) {
    			out.write(buf, 0, count);
    			count = 0;
    		}
       }
    }

    可见,Buffered I/O putStream每读/写一个字节,若要操作的数据在BUF中,就直接对内存的buf[]进行读/写操作;否则从磁盘相应位置填充buf[],再直接对内存的buf[]进行读/写操作,绝大部分的读/写操作是对内存buf[]的操作。

    1.3.小结

    内存存取时间单位是纳秒级(10E-9),磁盘存取时间单位是毫秒级(10E-3),同样操作一次的开销,内存比磁盘快了百万倍。理论上可以预见,即使对内存操作上万次,花费的时间也远少对于磁盘一次I/O的开销。显然后者是通过增加位于内存的BUF存取,减少磁盘I/O的开销,提高存取效率的,当然这样也增加了BUF控制部分的开销。从实际应用来看,存取效率提高了32倍。

    根据1.3得出的结论,现试着对RandomAccessFile类也加上缓冲读写机制。

    随机访问类与顺序类不同,前者是通过实现DataInput/DataOutput接口创建的,而后者是扩展FilterInputStream/FilterOutputStream创建的,不能直接照搬。

    2.1.开辟缓冲区BUF[默认:1024字节],用作读/写的共用缓冲区。

    2.2.先实现读缓冲。

    读缓冲逻辑的基本原理:

    • A 欲读文件POS位置的一个字节。

    • B 查BUF中是否存在?若有,直接从BUF中读取,并返回该字符BYTE。

    • C 若没有,则BUF重新定位到该POS所在的位置并把该位置附近的BUFSIZE的字节的文件内容填充BUFFER,返回B。

    以下给出关键部分代码及其说明:

    Java代码 复制代码 收藏代码
    1. public class BufferedRandomAccessFile extends RandomAccessFile {
    2. // byte read(long pos):读取当前文件POS位置所在的字节
    3. // bufstartpos、bufendpos代表BUF映射在当前文件的首/尾偏移地址。
    4. // curpos指当前类文件指针的偏移地址。
    5. public byte read(long pos) throws IOException {
    6. if (pos < this.bufstartpos || pos > this.bufendpos ) {
    7. this.flushbuf();
    8. this.seek(pos);
    9. if ((pos < this.bufstartpos) || (pos > this.bufendpos))
    10. throw new IOException();
    11. }
    12. this.curpos = pos;
    13. return this.buf[(int)(pos - this.bufstartpos)];
    14. }
    15. // void flushbuf():bufdirty为真,把buf[]中尚未写入磁盘的数据,写入磁盘。
    16. private void flushbuf() throws IOException {
    17. if (this.bufdirty == true) {
    18. if (super.getFilePointer() != this.bufstartpos) {
    19. super.seek(this.bufstartpos);
    20. }
    21. super.write(this.buf, 0, this.bufusedsize);
    22. this.bufdirty = false;
    23. }
    24. }
    25. // void seek(long pos):移动文件指针到pos位置,并把buf[]映射填充至POS所在的文件块。
    26. public void seek(long pos) throws IOException {
    27. if ((pos < this.bufstartpos) || (pos > this.bufendpos)) { // seek pos not in buf
    28. this.flushbuf();
    29. if ((pos >= 0) && (pos <= this.fileendpos) && (this.fileendpos != 0)) { // seek pos in file (file length > 0)
    30. this.bufstartpos = pos * bufbitlen / bufbitlen;
    31. this.bufusedsize = this.fillbuf();
    32. } else if (((pos == 0) && (this.fileendpos == 0)) || (pos == this.fileendpos + 1)) { // seek pos is append pos
    33. this.bufstartpos = pos;
    34. this.bufusedsize = 0;
    35. }
    36. this.bufendpos = this.bufstartpos + this.bufsize - 1;
    37. }
    38. this.curpos = pos;
    39. }
    40. // int fillbuf():根据bufstartpos,填充buf[]。
    41. private int fillbuf() throws IOException {
    42. super.seek(this.bufstartpos);
    43. this.bufdirty = false;
    44. return super.read(this.buf);
    45. }
    46. }

    public class BufferedRandomAccessFile extends RandomAccessFile {
    //  byte read(long pos):读取当前文件POS位置所在的字节
    //  bufstartpos、bufendpos代表BUF映射在当前文件的首/尾偏移地址。
    //  curpos指当前类文件指针的偏移地址。
        public byte read(long pos) throws IOException {
            if (pos < this.bufstartpos || pos > this.bufendpos ) {
                this.flushbuf();
                this.seek(pos);
                if ((pos < this.bufstartpos) || (pos > this.bufendpos)) 
                    throw new IOException();
            }
            this.curpos = pos;
            return this.buf[(int)(pos - this.bufstartpos)];
        }
    // void flushbuf():bufdirty为真,把buf[]中尚未写入磁盘的数据,写入磁盘。
        private void flushbuf() throws IOException {
            if (this.bufdirty == true) {
                if (super.getFilePointer() != this.bufstartpos) {
                    super.seek(this.bufstartpos);
                }
                super.write(this.buf, 0, this.bufusedsize);
                this.bufdirty = false;
            }
        }
    // void seek(long pos):移动文件指针到pos位置,并把buf[]映射填充至POS所在的文件块。
        public void seek(long pos) throws IOException {
            if ((pos < this.bufstartpos) || (pos > this.bufendpos)) { // seek pos not in buf
                this.flushbuf();
                if ((pos >= 0) && (pos <= this.fileendpos) && (this.fileendpos != 0)) {   // seek pos in file (file length > 0)
                	  this.bufstartpos =  pos * bufbitlen / bufbitlen;
                      this.bufusedsize = this.fillbuf();
                } else if (((pos == 0) && (this.fileendpos == 0)) || (pos == this.fileendpos + 1)) {   // seek pos is append pos
                    this.bufstartpos = pos;
                    this.bufusedsize = 0;
                }
                this.bufendpos = this.bufstartpos + this.bufsize - 1;
            }
            this.curpos = pos;
        }
    // int fillbuf():根据bufstartpos,填充buf[]。
        private int fillbuf() throws IOException {
            super.seek(this.bufstartpos);
            this.bufdirty = false;
            return super.read(this.buf);
        }
    }

    至此缓冲读基本实现,逐字节COPY一个12兆的文件(这里牵涉到读和写,用BufferedRandomAccessFile试一下读的速度):

    耗用时间(秒)
    RandomAccessFile RandomAccessFile 95.848
    BufferedRandomAccessFile BufferedOutputStream + DataOutputStream 2.813
    BufferedInputStream + DataInputStream BufferedOutputStream + DataOutputStream 2.935

    可见速度显著提高,与BufferedInputStream+DataInputStream不相上下。

    2.3.实现写缓冲。

    写缓冲逻辑的基本原理:

    • A欲写文件POS位置的一个字节。

    • B 查BUF中是否有该映射?若有,直接向BUF中写入,并返回true。

    • C若没有,则BUF重新定位到该POS所在的位置,并把该位置附近的 BUFSIZE字节的文件内容填充BUFFER,返回B。

    下面给出关键部分代码及其说明:

    Java代码 复制代码 收藏代码
    1. // boolean write(byte bw, long pos):向当前文件POS位置写入字节BW。
    2. // 根据POS的不同及BUF的位置:存在修改、追加、BUF中、BUF外等情况。在逻辑判断时,把最可能出现的情况,最先判断,这样可提高速度。
    3. // fileendpos:指示当前文件的尾偏移地址,主要考虑到追加因素
    4. public boolean write(byte bw, long pos) throws IOException {
    5. if ((pos >= this.bufstartpos) && (pos <= this.bufendpos)) { // write pos in buf
    6. this.buf[(int)(pos - this.bufstartpos)] = bw;
    7. this.bufdirty = true;
    8. if (pos == this.fileendpos + 1) { // write pos is append pos
    9. this.fileendpos++;
    10. this.bufusedsize++;
    11. }
    12. } else { // write pos not in buf
    13. this.seek(pos);
    14. if ((pos >= 0) && (pos <= this.fileendpos) && (this.fileendpos != 0)) { // write pos is modify file
    15. this.buf[(int)(pos - this.bufstartpos)] = bw;
    16. } else if (((pos == 0) && (this.fileendpos == 0)) || (pos == this.fileendpos + 1)) { // write pos is append pos
    17. this.buf[0] = bw;
    18. this.fileendpos++;
    19. this.bufusedsize = 1;
    20. } else {
    21. throw new IndexOutOfBoundsException();
    22. }
    23. this.bufdirty = true;
    24. }
    25. this.curpos = pos;
    26. return true;
    27. }

    // boolean write(byte bw, long pos):向当前文件POS位置写入字节BW。
    // 根据POS的不同及BUF的位置:存在修改、追加、BUF中、BUF外等情况。在逻辑判断时,把最可能出现的情况,最先判断,这样可提高速度。
    // fileendpos:指示当前文件的尾偏移地址,主要考虑到追加因素
        public boolean write(byte bw, long pos) throws IOException {
            if ((pos >= this.bufstartpos) && (pos <= this.bufendpos)) { // write pos in buf
                this.buf[(int)(pos - this.bufstartpos)] = bw;
                this.bufdirty = true;
                if (pos == this.fileendpos + 1) { // write pos is append pos
                    this.fileendpos++;
                    this.bufusedsize++;
                }
            } else { // write pos not in buf
                this.seek(pos);
                if ((pos >= 0) && (pos <= this.fileendpos) && (this.fileendpos != 0)) { // write pos is modify file
                    this.buf[(int)(pos - this.bufstartpos)] = bw;
                } else if (((pos == 0) && (this.fileendpos == 0)) || (pos == this.fileendpos + 1)) { // write pos is append pos
                    this.buf[0] = bw;
                    this.fileendpos++;
                    this.bufusedsize = 1;
                } else {
                    throw new IndexOutOfBoundsException();
                }
                this.bufdirty = true;
            }
            this.curpos = pos;
            return true;
        }
    	

    至此缓冲写基本实现,逐字节COPY一个12兆的文件,(这里牵涉到读和写,结合缓冲读,用BufferedRandomAccessFile试一下读/写的速度):

    耗用时间(秒)
    RandomAccessFile RandomAccessFile 95.848
    BufferedInputStream + DataInputStream BufferedOutputStream + DataOutputStream 2.935
    BufferedRandomAccessFile BufferedOutputStream + DataOutputStream 2.813
    BufferedRandomAccessFile BufferedRandomAccessFile 2.453

    可见综合读/写速度已超越BufferedInput/OutputStream+DataInput/OutputStream

  • 相关阅读:
    JVM学习(2):类加载器
    JVM学习(1):类加载机制
    MySQL优化(7):其他注意事项
    MySQL优化(6):分表和读写分离
    MySQL优化(5):分区
    MySQL优化(4):查询缓存
    MySQL优化(3):索引
    关于博客
    【题解】Telephone Lines
    【题解】神经网络
  • 原文地址:https://www.cnblogs.com/finalstar/p/2204880.html
Copyright © 2020-2023  润新知