当前位置: 首页 > 技术随笔 > Java nio入门教程详解(十九)

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

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 比较
FileChannelRandomAccessFilePOSIX 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 有两个空洞的磁盘文件图 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入门教程详解(二十)

2 0
我们认为: 用户的主要目的,是为了获取有用的信息,而不是来点击广告的。因此本站将竭力做好内容,并将广告和内容进行分离,确保所有广告不会影响到用户的正常阅读体验。用户仅凭个人意愿和兴趣爱好点击广告。
我们坚信:只有给用户带来价值,用户才会给我们以回报。
CodePlayer技术交流群1CodePlayer技术交流群1

帮朋友打一个硬广告:

P2P网贷系统(Java版本) 新年低价大促销,多年P2P技术积累,系统功能完善(可按需定制,可支持第三方存管、银行存管),架构稳定灵活、性能优异、二次开发快速简单。 另可提供二次开发、安装部署、售后维护、安全培训等一条龙服务。

外行看热闹,内行看门道。可以自信地认为,在系统设计上,比市面上的晓风、迪蒙、方维、绿麻雀、国融信、金和盛等P2P系统要好。
深圳地区支持自带技术人员现场考察源代码、了解主要技术架构,货比三家,再决定是否购买。

也可推荐他人购买,一旦完全成交,推荐人可获得实际售价 10% 的返现。
有意向者,详情请 点击这里 联系,工作时间立即回复。