您的浏览器过于古老 & 陈旧。为了更好的访问体验, 请 升级你的浏览器
Ready 发布于2013年08月18日 10:10

原创 Java nio入门教程详解(十九)

9 次浏览 读完需要≈ 16 分钟

内容目录

3.3.1 访问文件

每个FileChannel对象都同一个文件描述符(file descriptor)有一对一的关系,所以上面列出的API 方法与在您最喜欢的 POSIX(可移植操作系统接口)兼容的操作系统上的常用文件 I/O 系统调用紧密对应也就不足为怪了。名称也许不尽相同,不过常见的suspect(“可疑分子”)都被集中起来了。您可能也注意到了上面列出的 API 方法同java.io包中RandomAccessFile类的方法的相似之处了。本质上讲,RandomAccessFile类提供的是同样的抽象内容。在通道出现之前,底层的文件操作都是通过RandomAccessFile类的方法来实现的。FileChannel模拟同样的 I/O 服务,因此它的API自然也是很相似的。

为了便于比较,表 3-1 列出了 FileChannelRandomAccessFile POSIX I/O system calls 三者在方法上的对应关系。

表 3-1 File I/O API 比较
FileChannel RandomAccessFile POSIX system call
read() read() read()
write() write() write()
size() length() fstat()
position() getFilePointer() lseek()
position (long newPosition) seek() lseek()
truncate() setLength() ftruncate()
force() getFD().sync() fsync()

让我们来进一步看下基本的文件访问方法(请记住这些方法都可以抛出 java.io.IOException 异常):

public abstract class FileChannel extends AbstractChannel implements ByteChannel, GatheringByteChannel, ScatteringByteChannel {
    // 这里仅列出部分API
    public abstract long position()
    public abstract void position (long newPosition)
    public abstract int read (ByteBuffer dst)
    public abstract int read (ByteBuffer dst, long position)
    public abstract int write (ByteBuffer src)
    public abstract int write (ByteBuffer src, long position)
    public abstract long size()
    public abstract void truncate (long size)
    public abstract void force (boolean metaData)
}

同底层的文件描述符一样,每个FileChannel都有一个叫“file position”的概念。这个position值决定文件中哪一处的数据接下来将被读或者写。从这个方面看,FileChannel类同缓冲区很类似,并且MappedByteBuffer类使得我们可以通过ByteBuffer API来访问文件数据(我们会在后面的章节中了解到这一点)。

您可以从前面的API清单中看到,有两种形式的position()方法。第一种,不带参数的,返回当前文件的position值。返回值是一个长整型(long),表示文件中的当前字节位置。

第二种形式的position()方法带一个long(长整型)参数并将通道的position设置为指定值。如果尝试将通道position设置为一个负值会导致java.lang.IllegalArgumentException异常,不过可以把position设置到超出文件尾,这样做会把position设置为指定值而不改变文件大小。假如在将position设置为超出当前文件大小时实现了一个read()方法,那么会返回一个文件尾(end-of-file) 条件;倘若此时实现的是一个write()方法则会引起文件增长以容纳写入的字节,具体行为类似于实现一个绝对write()并可能导致出现一个文件空洞(file hole,参见《什么是文件空洞》)。

FileChannel位置(position)是从底层的文件描述符获得的,该position同时被作为通道引用获取来源的文件对象共享。这也就意味着一个对象对该position的更新可以被另一个对象看到:

RandomAccessFile randomAccessFile = new RandomAccessFile ("filename", "r");
// Set the file position
randomAccessFile.seek (1000);
// Create a channel from the file
FileChannel fileChannel = randomAccessFile.getChannel();
// This will print "1000"
System.out.println ("file pos: " + fileChannel.position());
// Change the position using the RandomAccessFile object
randomAccessFile.seek (500);
75
// This will print "500"
System.out.println ("file pos: " + fileChannel.position());
// Change the position using the FileChannel object
fileChannel.position (200);
// This will print "200"
System.out.println ("file pos: " + randomAccessFile.getFilePointer());

类似于缓冲区的get()put()方法,当字节被read()write()方法传输时,文件position会自动更新。如果position值达到了文件大小的值(文件大小的值可以通过size()方法返回),read()方法会返回一个文件尾条件值(-1)。可是,不同于缓冲区的是,如果实现write()方法时position前进到超过文件大小的值,该文件会扩展以容纳新写入的字节。

同样类似于缓冲区,也有带position参数的绝对形式的read()write()方法。这种绝对形式的方法在返回值时不会改变当前的文件position。由于通道的状态无需更新,因此绝对的读和写可能会更加有效率,操作请求可以直接传到本地代码。更妙的是,多个线程可以并发访问同一个文件而不会相互产生干扰。这是因为每次调用都是原子性的(atomic),并不依靠调用之间系统所记住 的状态。

图 3-8 有两个空洞的磁盘文件

尝试在文件末尾之外的position进行一个绝对读操作,size()方法会返回一个 end-of-file。在超出文件大小的 position上做一个绝对write()会导致文件增加以容纳正在被写入的新字节。文件中位于之前 end-of-file 位置和新添加的字节起始位置之间区域的字节的值不是由FileChannel类指定,而是在大多数情况下反映底层文件系统的语义。取决于操作系统和(或)文件系统类型,这可能会导致在文件中出现一个空洞。

当需要减少一个文件的size时,truncate()方法会砍掉您所指定的新size值之外的所有数据。如果当前size大于新size,超出新size的所有字节都会被悄悄地丢弃。如果提供的新size值大于或等于当前的文件size值,该文件不会被修改。这两种情况下,truncate()都会产生副作用:文件的position会被设置为所提供的新size值。

public abstract class FileChannel extends AbstractChannel implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
{
    // 这里仅列出部分API
    public abstract void truncate (long size)
    public abstract void force (boolean metaData)
}

上面列出的最后一个API是force()。该方法告诉通道强制将全部待定的修改都应用到磁盘的文件上。所有的现代文件系统都会缓存数据和延迟磁盘文件更新以提高性能。调用force()方法要求文件的所有待定修改立即同步到磁盘。

如果文件位于一个本地文件系统,那么一旦force()方法返回,即可保证从通道被创建(或上次调用force())时起的对文件所做的全部修改已经被写入到磁盘。对于关键操作如事务(transaction)处理来说,这一点是非常重要的,可以保证数据完整性和可靠的恢复。然而,如果文件位于一个远程的文件系统,如NFS上,那么不能保证待定修改一定能同步到永久存储器(permanent storage)上,因 Java 虚拟机不能做操作系统或文件系统不能实现的承诺。如果您的程序在面临系统崩溃时必须维持数据完整性,先去验证一下您在使用的操作系统和(或)文件系统在同步修改方面是可以依赖的。

force()方法的布尔型参数表示在方法返回值前文件的元数据(metadata)是否也要被同步更新到磁盘。元数据指文件所有者、访问权限、最后一次修改时间等信息。大多数情形下,该信息对数据恢复而言是不重要的。给force()方法传递false值表示在方法返回前只需要同步文件数据的更改。大多数情形下,同步元数据要求操作系统进行至少一次额外的底层I/O操作。一些大数量事务处理程序可能通过在每次调用force()方法时不要求元数据更新来获取较高的性能提升,同时也不会牺牲数据完整性。

Java nio入门教程详解(二十)

  • CodePlayer技术交流群1
  • CodePlayer技术交流群2

0 条评论

撰写评论