Java IO (2) -- RandomAccessFile 类

1. 概念

RandomAccessFile 既可以读取文件内容,也可以向文件输出数据。翻看源码可知 RandomAccessFile 继承了 DataOutput 和DataInput 两个接口,而字节流体系中 DataOutputStream 和 DataInputStream 也分别继承了 DataOutput 接口和 DataInput 接口,所以 RandomAccessFile 类中包含了 DataOutputStream 和 DataInputStream 类中所有方法,有些方法直接调用 “字节数据流” 中方法,如其中 WriteUTF() 方法直接调用 DataOutputStream 类中 WriteUTF() 方法,ReaderUTF() 方法直接调用 DataInputStream 类中 ReadUTF() 方法;

同时,RandomAccessFile 支持 随机访问 的方式,程序快可以直接跳转到文件的任意地方来读写数据。原理大概就是:将文件看成是一个大型的字节数组,通过游标(cursor)或者移动文件指针对数组中任意位置字节读取或者写入,从而达到对文件进行随机访问的目的;由于 RandomAccessFile 可以自由访问文件的任意位置,所以如果需要访问文件的部分内容,而不是把文件从头读到尾,使用 RandomAccessFile 将是更好的选择。

与 OutputStream、Writer 等输出流不同的是,RandomAccessFile 允许自由定义文件记录指针,RandomAccessFile 可以不从开始的地方开始输出,因此 RandomAccessFile 可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用 RandomAccessFile。

RandomAccessFile 的方法虽然多,但它有一个最大的局限,就是只能读写文件,不能读写其他 IO 节点。

RandomAccessFile 的一个重要使用场景就是网络请求中的多线程下载及断点续传。

2. 字段

  1. private FileDescriptor fd:与流关联的文件描述符对象
  2. private FileChannel channel = null:读取文件的通道
  3. private boolean rw:是否有读写的权限
  4. private final String path:文件的路径
  5. private Object closeLock = new Object():文件关闭对象锁
  6. private volatile boolean closed = false:流是否关闭的标识
  7. private static final int O_RDONLY = 1:对应读写模式中"r"
  8. private static final int O_RDWR = 2:对应读写模式中"rw"
  9. private static final int O_SYNC = 4:对应读写模式中"rws"
  10. private static final int O_DSYNC = 8:对应读写模式中"rwd"

3. 方法

1. 构造器

  1. RandomAccessFile(String name, String mode)
  2. RandomAccessFile(File file, String mode)

RandomAccessFile 类有两个构造函数,其实这两个构造函数基本相同,只不过是指定文件的形式不同:一个需要使用 String 参数来指定文件名,一个使用 File 参数来指定文件本身。除此之外,创建 RandomAccessFile 对象时还需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式,一共有 4 种模式:

  1. “r”:以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException
  2. “rw”:以读写方式打开,以便读取和写入
  3. “rws”:以读写方式打开,以便读取和写入。相对于 “rw”,“rws” 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备
  4. “rwd”:以读写方式打开,以便读取和写入,相对于 “rw”,“rwd” 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。

2. 两个重要的方法

RandomAccessFile 既可以读文件,也可以写文件,所以类似于 InputStream 的 read() 方法,以及类似于 OutputStream 的 write() 方法,RandomAccessFile 都具备。除此之外,RandomAccessFile 具备两个特有的方法,来支持其随机访问的特性。

RandomAccessFile 对象包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个 RandomAccessFile 对象时,该对象的文件指针记录位于文件头(也就是 0 处),当读/写了 n 个字节后,文件记录指针将会后移 n 个字节。除此之外,RandomAccessFile 还可以自由移动该记录指针。下面就是 RandomAccessFile 具有的两个特殊方法,来操作记录指针,实现随机访问:

  1. long getFilePointer( ):返回文件记录指针的当前位置
  2. void seek(long pos ):将文件指针定位到 pos 位置

3. 其他方法

  1. public final FileDescriptor getFD():返回与流关联的文件描述符对象
  2. public final FileChannel getChannel():返回与文件关联的通道
  3. public int read():读取单个字节
  4. public int read(byte b[], int off, int len):从文件读取最多len个字节到字节数组b中
  5. public int read(byte b[]):从文件中读取最多b.length个字节到字节数组b中
  6. public final void readFully(byte b[]):从当前文件指针的位置开始读"满" b.length 个字节到字节数组b中,在读取b.length个字节之前,方法一直阻塞,除非流结束或者抛出异常
  7. public final void readFully(byte b[], int off, int len):从文件中当前文件指针开始读"满"len个字节到字节数组b中.在读取len个字节之前,方法一直阻塞,除非流结束或者抛出异常
  8. public int skipBytes(int n):跳过n个字节,可能存在由于文件中剩余可读取的字节数比要跳过字节n小,导致实际跳过字节小于n
  9. public void write(int b):从当前文件指针开始,写入一个字节到文件中
  10. public void write(byte b[]):将字节数组b写到文件中
  11. public void write(byte b[], int off, int len):将字节数组b中off位置开始,len个字节写到文件中
  12. public native long getFilePointer():返回文件中当前偏移量,即当前文件指针位置
  13. public void seek(long pos):设置文件指针偏移量.表示的是文件中下一个读取数据位置
  14. public native long length():返回文件长度(针对字节)
  15. public native void setLength(long newLeng):设置文件的长度,存在两种情况: 1.文件长度length>参数newLength,文件将会被截断,调用此方法前,getFilePointer()>newLength,调用此方法后offset=newlength. 2.文件长度length<参数newLenght,文件将会扩展,扩展部分的内容未定义
  16. public void close(){}:关闭流,释放关联资源

以下这些方法与DataInputStream和DataOutputStream中方法一样,有的是直接调用DataOutputStream和DataInputStream中的方法:

  1. public final boolean readBoolean(){}
  2. public final byte readByte(){}
  3. public final int readUnsignedByte(){}
  4. public final short readShort(){}
  5. public final int readUnsignedShort(){}
  6. public final char readChar(){}
  7. public final int readInt(){}
  8. public final long readLong(){}
  9. public final float readFloat(){}
  10. public final double readDouble(){}
  11. public final String readLine(){}
  12. public final String readUTF(){}
  13. public final void writeBoolean(boolean v)
  14. public final void writeByte(int v){}
  15. public final void writeShort(int v){}
  16. public final void writeChar(int v){}
  17. public final void writeInt(int v){}
  18. public final void writeLong(long v){}
  19. public final void writeFloat(float v){}
  20. public final void writeDouble(double v){}
  21. public final void writeChars(String s){}
  22. public final void writeUTF(String str){}

4. 案例

案例1:利用 RandomAccessFile 实现文件的多线程下载,即多线程下载一个文件时,将文件分成几块,每块用不同的线程进行下载。下面是一个利用多线程在写文件时的例子,其中预先分配文件所需要的空间,然后在所分配的空间中进行分块,然后写入

/** 
 * 测试利用多线程进行文件的写操作 
 */  
public class Test {  
  
    public static void main(String[] args) throws Exception {  
        // 预分配文件所占的磁盘空间,磁盘中会创建一个指定大小的文件  
        RandomAccessFile raf = new RandomAccessFile("D://abc.txt", "rw");  
        raf.setLength(1024*1024); // 预分配 1M 的文件空间  
        raf.close();  
          
        // 所要写入的文件内容  
        String s1 = "第一个字符串";  
        String s2 = "第二个字符串";  
        String s3 = "第三个字符串";  
        String s4 = "第四个字符串";  
        String s5 = "第五个字符串";  
          
        // 利用多线程同时写入一个文件  
        new FileWriteThread(1024*1,s1.getBytes()).start(); // 从文件的1024字节之后开始写入数据  
        new FileWriteThread(1024*2,s2.getBytes()).start(); // 从文件的2048字节之后开始写入数据  
        new FileWriteThread(1024*3,s3.getBytes()).start(); // 从文件的3072字节之后开始写入数据  
        new FileWriteThread(1024*4,s4.getBytes()).start(); // 从文件的4096字节之后开始写入数据  
        new FileWriteThread(1024*5,s5.getBytes()).start(); // 从文件的5120字节之后开始写入数据  
    }  
      
    // 利用线程在文件的指定位置写入指定数据  
    static class FileWriteThread extends Thread{  
        private int skip;  
        private byte[] content;  
          
        public FileWriteThread(int skip,byte[] content){  
            this.skip = skip;  
            this.content = content;  
        }  
          
        public void run(){  
            RandomAccessFile raf = null;  
            try {  
                raf = new RandomAccessFile("D://abc.txt", "rw");  
                raf.seek(skip);  
                raf.write(content);  
            } catch (FileNotFoundException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            } finally {  
                try {  
                    raf.close();  
                } catch (Exception e) {  
                }  
            }  
        }  
    }  
}  

当 RandomAccessFile 向指定文件中插入内容时,将会覆盖掉原有内容。如果不想覆盖掉,则需要将原有内容先读取出来,然后先把插入内容插入后再把原有内容追加到插入内容后。

案例2:

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();
	}
}
//结果
this is demo of RandomAccessFile
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 // 利用了 seek 覆盖了数据
this is demo of RandomAccessFile
2
a
true
1234
123456
123456789
1.732
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值