内容目录
3.3.1 访问文件
每个FileChannel
对象都同一个文件描述符(file descriptor)有一对一的关系,所以上面列出的API 方法与在您最喜欢的 POSIX(可移植操作系统接口)兼容的操作系统上的常用文件 I/O 系统调用紧密对应也就不足为怪了。名称也许不尽相同,不过常见的suspect(“可疑分子”)都被集中起来了。您可能也注意到了上面列出的 API 方法同java.io
包中RandomAccessFile
类的方法的相似之处了。本质上讲,RandomAccessFile
类提供的是同样的抽象内容。在通道出现之前,底层的文件操作都是通过RandomAccessFile
类的方法来实现的。FileChannel
模拟同样的 I/O 服务,因此它的API自然也是很相似的。
为了便于比较,表 3-1 列出了 FileChannel
、RandomAccessFile
和 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),并不依靠调用之间系统所记住 的状态。
尝试在文件末尾之外的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()
方法时不要求元数据更新来获取较高的性能提升,同时也不会牺牲数据完整性。
0 条评论
撰写评论