Java I/O 学习总结(二)

来源:互联网 发布:二手域名 编辑:程序博客网 时间:2021/08/03 19:46
上一篇介绍了Java I/O 一些基础的概念和File类,File类是针对文件本身操作的,不可以对文件的内容进行操作,本文要介绍的RandomAccessFile类是可以对文件内容进行随机存取的。

RandomAccessFile

  提供对文件内容的访问,可以读文件,也可以写文件,同时支持随机访问文件,可以访问文件的任意位置。

下面先来看一下构造方法源码:

public RandomAccessFile(String name, String mode)        throws FileNotFoundException    {        this(name != null ? new File(name) : null, mode);    }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);    }

通过上述源码我们可以发现,第一个构造函数的实质是调用第二个构造函数,所以这里我们主要关注第二个构造函数。

此构造方法有两个参数,要想打开一个文件,需要先获得这个文件的File实例对象,第二个参数则是文件访问模式mode。

从源码中我们可以看到mode有四种:

  r:只读模式,只能从该文件中读取数据,不能写入;

  rw:读写模式,对该文件既可读取数据,也可以写入数据,如果该文件尚不存在,则尝试创建该文件;

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

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

对于后两种模式可能有些难懂,下面是JDK API 1.6.0版本中的定义:

"rws""rwd" 模式的工作方式极其类似 FileChannel 类的 force(boolean) 方法,分别传递 truefalse 参数,除非它们始终应用于每个 I/O 操作,并因此通常更为高效。如果该文件位于本地存储设备上,那么当返回此类的一个方法的调用时,可以保证由该调用对此文件所做的所有更改均被写入该设备。这对确保在系统崩溃时不会丢失重要信息特别有用。如果该文件不在本地设备上,则无法提供这样的保证。

"rwd" 模式可用于减少执行的 I/O 操作数量。使用 "rwd" 仅要求更新要写入存储的文件的内容;使用 "rws" 要求更新要写入的文件内容及其元数据,这通常要求至少一个以上的低级别 I/O 操作。

也就是说,每次write数据时,"rw"模式,数据不会立即写到硬盘中而“rwd”,数据会被立即写入硬盘。如果写数据过程发生异常,“rwd”模式中已被write的数据被保存到硬盘,而“rw"则全部丢失。

接下来通过一段简单的代码来了解一下RandomAccessFile类的具体作用,这里顺带讲一下常用的几个编码:

 1 public static void main(String[] args) throws IOException{ 2         File file = new File("demo"); 3         if(!file.exists()){ 4             file.mkdir(); 5         } 6         File demo = new File(file, "raf.dat"); 7         if(!demo.exists()){ 8             demo.createNewFile(); 9         }10 11         RandomAccessFile raf = new RandomAccessFile(demo, "rw");12         //指针的位置13         System.out.println(raf.getFilePointer());14 15         raf.write('A'); //write只写一个字节,后8位16         System.out.println(raf.getFilePointer());17         raf.write('B');18 19         int i = 0x7fffffff;20         //write方法每次只能写一个字节,如果要把这个i写进去需要4次21         raf.write(i >>> 24);//高8位22         raf.write(i >>> 16);23         raf.write(i >>> 8);24         raf.write(i);25         System.out.println(raf.getFilePointer());26 27         //可以直接写一个int28         raf.writeInt(i);29 30         String s = "中";31         byte[] gbk = s.getBytes("GBK");32         raf.write(gbk);33         System.out.println(raf.getFilePointer());34 35         //读文件,必须把指针移到头部36         raf.seek(0);37         //一次性读取,把文件中的内容都读到字节数组中38         byte[] buf = new byte[(int)raf.length()];39         raf.read(buf);40         System.out.println(Arrays.toString(buf));41         raf.close();42     }

第二行获取一个File对象,而参数没有给具体路径,那就直接默认是你项目所在的路径下创建该目录。在该文件下创建raf.dat文件。

11行,获取raf.dat文件的RandomAccessFile对象,以rw模式打开。

raf.getFilePointer() 用来获取文件指针的位置,返回一个int类型的数值。此时是空文件,所以12行输出结果为0

15行使用write()方法写入数据,write()方法一次只能写入一个字节(后8位),同时指针指向下一个位置,准备再次写入,所以16行输出结果为1

19行定义一个二进制32位的int型变量,因为write()方法一次只写一个字节,所以需要4次才能将该数据写入文件,通过移位操作依次写入

15,17,21-24行共写入6次,所以25行输出6

28行中,RandomAccessFile类提供了直接写入一个int类型值的方法writeInt(),我们来看一下这个方法的源码:

 1 /** 2      * Writes an {@code int} to the file as four bytes, high byte first. 3      * The write starts at the current position of the file pointer. 4      * 5      * @param      v   an {@code int} to be written. 6      * @exception  IOException  if an I/O error occurs. 7      */ 8     public final void writeInt(int v) throws IOException { 9         write((v >>> 24) & 0xFF);10         write((v >>> 16) & 0xFF);11         write((v >>>  8) & 0xFF);12         write((v >>>  0) & 0xFF);13         //written += 4;14     }

可以发现,该方法的实现原理就是分4次写入一个int型数据,位与上0xFF是为了保留低8位,把前面的0都清除掉,看了源码是不是恍然大悟~

接着看代码,30行定义一个中文字符,31行以“GBK”的编码方式将其转换为字节数组,write()方法可以直接写入一个字节数组,33行输出12

小谈一下字符编码,GBK编码:中文字符占2个字节,英文字符占1个字节;

         UTF-8编码:中文字符占3个字节,英文字符占1个字节;

           UTF-16BE编码:中文字符占2个字节,英文字符占2个字节;

所以33行输出12,如果将31的编码改为UTF-8,再运行,可以发现,33行将会输出13

36行seek()方法将文件指针移动到头部,然后一次性读取,将文件内容全部读取到字节数组中

41行,对文件内容操作(读写)完成后,一定要执行close()方法进行关闭(Oracle官方说明)

RandomAccessFile类的基本流程和操作就介绍到这里了,有不对的地方还请广大程序员多多指正~

0 0