Java nio 之 FileChannel

本文深入探讨了Java NIO中FileChannel的特性及其与RandomAccessFile及POSIX I/O systemcalls的关系。文章详细介绍了FileChannel的线程安全性、文件空洞的概念、read/write方法的工作原理以及force()方法的重要性。


本文整理自《Java NIO》一书。


FileChannel对象是线程安全(thread-safe)的。多个进程可以在同一个实例上并发调用方法而不会引起任何问题,不过并非所有的操作都是多线程的(multithreaded)。影响通道位置或者影响文件大小的操作都是单线程的(single-threaded)。如果有一个线程已经在执行会影响通道位置或文件大小的操作,那么其他尝试进行此类操作之一的线程必须等待。并发行为也会受到底层的操作系统或文件系统影响。

同大多数I/O相关的类一样,FileChannel是一个反映Java虚拟机外部一个具体对象的抽象。FileChannel类保证同一个Java虚拟机上的所有实例看到的某个文件的视图均是一致的,但是Java虚拟机却不能对超出它控制范围的因素提供担保。通过一个FileChannel实例看到的某个文件的视图同通过一个外部的非Java进程看到的该文件的视图可能一致,也可能不一致。多个进程发起的并发文件访问的语义高度取决于底层的操作系统和(或)文件系统。一般而言,由运行在不同Java虚拟机上的FileChannel对象发起的对某个文件的并发访问和由非Java进程发起的对该文件的并发访问是一致的

下面列出FileChannel、RandomAccessFile和POSIX(可移植操作系统接口) I/O system calls三者在方法上的对应关系:



<    文件空洞(file hole)

在UNIX文件操作中,文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被设为 0。
如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造“空洞(hole)”。没有被实际写入文件的所有字节由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的
用ls查看的文件大小是将空洞算在内的。
cp命令拷贝的文件,空洞部分不拷贝,所以生成的同样文件占用磁盘空间小。
用read读取空洞部分读出的数据是0,所以如果用read和write拷贝一个有空洞的文件,那么最终得到的文件没有了空洞,空洞部分都被0给填充了,文件占用的磁盘空间就大了。不过文件大小不变。
空洞文件作用很大,例如迅雷下载文件,在未下载完成时就已经占据了全部文件大小的空间,这时候就是空洞文件。下载时如果没有空洞文件,多线程下载时文件就都只能从一个地方写入,这就不是多线程了。如果有了空洞文件,可以从不同的地址写入,就完成多线程的优势任务>


类似于缓冲区的get( ) 和put( )方法,FileChannel中,当字节被read( )或write( )方法传输时,文件position会自动更新。如果position值达到了文件大小的值(文件大小的值可以通过size( )方法返回),read( )方法会返回一个文件尾条件值(-1)。可是,不同于缓冲区的是,如果实现write( )方法时position前进到超过文件大小的值,该文件会扩展以容纳新写入的字节。同样类似于缓冲区,也有带position参数的绝对形式的read( )和write( )方法。这种绝对形式的方法在返回值时不会改变当前的文件position。由于通道的状态无需更新,因此绝对的读和写可能会更加有效率,操作请求可以直接传到本地代码。更妙的是,多个线程可以并发访问同一个文件而不会相互产生干扰。这是因为每次调用都是原子性的(atomic),并不依靠调用之间系统所记住的状态。
如果文件位于一个本地文件系统,那么一旦force( )方法返回,即可保证从通道被创建(或上次调用force( ))时起的对文件所做的全部修改已经被写入到磁盘。对于关键操作如事务(transaction)处理来说,这一点是非常重要的,可以保证数据完整性和可靠的恢复。然而,如果文件位于一个远程的文件系统,如NFS上,那么不能保证待定修改一定能同步到永久存储器(permanent storage)上,因Java虚拟机不能做操作系统或文件系统不能实现的承诺。如果您的程序在面临系统崩溃时必须维持数据完整性,先去验证一下您在使用的操作系统和(或)文件系统在同步修改方面是可以依赖的。
force( )方法的布尔型参数表示在方法返回值前文件的元数据(metadata)是否也要被同步更新到磁盘。元数据指文件所有者、访问权限、最后一次修改时间等信息。大多数情形下,该信息对数据恢复而言是不重要的。给force( )方法传递false值表示在方法返回前只需要同步文件数据的更改。大多数情形下,同步元数据要求操作系统进行至少一次额外的底层I/O操作。一些大数量事务处理程序可能通过在每次调用force( )方法时不要求元数据更新来获取较高的性能提升,同时也不会牺牲数据完整性。
如果一个线程在某个文件上获得了一个独占锁,然后第二个线程利用一个单独打开的通道来请求该文件的独占锁,那么第二个线程的请求会被批准。但如果这两个线程运行在不同的Java虚拟机上,那么第二个线程会阻塞,因为锁最终是由操作系统或文件系统来判优的并且几乎总是在进程级而非线程级上判优file lock 锁与文件关联,而不是与通道或文件句柄关联。我们使用锁来判优外部进程,而不是判优同一个Java虚拟机上的线程。如果您在使用完一个锁后而不释放它的话,可能会导致冲突或者死锁。请小心管理文件锁以避免出现此问题。一旦您成功地获取了一个文件锁,如果随后在通道上出现错误的话,请务必释放这个锁finally{ lock.release() }
FileLock也是线程安全的,多个线程可以并发访问同一个锁对象实例。


以下出自taobao@boyan 《NIO trick and trap》





### 回答1: FileChannelJava NIO中的一个类,它用于从文件中读取数据或向文件写入数据。FileChannel是非阻塞的,这意味着当调用FileChannel的读写方法时,线程不会被阻塞。相反,它会立即返回一个Future对象,你可以在后面调用这个对象的get()方法来获取实际读写操作的结果。 例如,你可以使用FileChannel来读取文件的内容,代码如下: ``` Path path = Paths.get("file.txt"); FileChannel channel = FileChannel.open(path, StandardOpenOption.READ); ByteBuffer buffer = ByteBuffer.allocate(1024); Future<Integer> result = channel.read(buffer, 0); int bytesRead = result.get(); ``` 你也可以使用FileChannel向文件写入数据,代码如下: ``` Path path = Paths.get("file.txt"); FileChannel channel = FileChannel.open(path, StandardOpenOption.WRITE); ByteBuffer buffer = ByteBuffer.wrap("Hello, World!".getBytes()); Future<Integer> result = channel.write(buffer, 0); int bytesWritten = result.get(); ``` 需要注意的是,FileChannel只能用于文件,如果你想要从网络或其他非文件源读取数据,你需要使用Java NIO中的其他类,例如SocketChannel或ServerSocketChannel。 ### 回答2: FileChannelJava NIO中表示文件的通道。它是对文件进行读取和写入的通道,类似于传统IO中的InputStream和OutputStream。 通过FileChannel,我们可以使用缓冲区(Buffer)来读取和写入文件的数据。我们可以创建一个FileChannel对象来操作文件,这可以通过FileInputStream、FileOutputStream或RandomAccessFile等类的getChannel()方法来获取。 FileChannel支持阻塞式和非阻塞式的IO操作。我们可以使用read()方法从通道中读取数据,使用write()方法将数据写入通道。还可以使用transferTo()和transferFrom()方法在通道之间直接传输数据,避免了传统IO中需要使用缓冲区来进行数据复制的过程。 FileChannel还提供了其他的一些有用的方法,比如position()和position(long newPosition)用于获取和设置文件指针的位置;size()方法用于获取文件的大小;truncate(long size)用于截断文件等。 在操作文件时,通常需要首先打开一个FileChannel,然后创建一个缓冲区来进行数据的读取和写入。一般的操作流程是,首先从通道中读取数据到缓冲区中,然后进行相应的处理,最后将数据写入到文件中。 总之,FileChannelJava NIO中用于操作文件的通道,它提供了更加灵活和高效的文件操作方式。通过FileChannel,我们可以使用缓冲区来读取和写入文件的数据,还可以直接在通道之间传输数据。同时,FileChannel还提供了一些方便的方法来操作文件的指针和大小等属性。使用FileChannel可以提高文件IO操作的效率和灵活性。 ### 回答3: NIO(New Input/Output)中的FileChannel是用于文件的通道类,它提供了一种在文件中读取、写入和操作数据的方式。 FileChannel可以通过Java程序对文件进行各种操作,包括读取文件内容、写入数据到文件,以及对文件指定位置进行读写操作等。 FileChannel的一些主要功能包括: 1. 读取文件内容:可以使用FileChannel的read()方法来读取文件中的数据,可以一次读取一定数量的字节,也可以读取整个文件的内容。 2. 写入文件内容:使用FileChannel的write()方法,可以将数据写入文件,可以一次写入一定数量的字节,也可以覆盖原有内容或者追加到文件末尾。 3. 文件位置操作:FileChannel的position()方法可以获取当前位置,也可以通过position(long newPosition)方法设置新的位置。可以根据实际需要在文件中进行定位,进行读写操作。 4. 通道之间的传输:FileChannel提供了transferFrom()和transferTo()方法用于直接在通道之间传输数据,可以高效地传输大量数据。 5. 文件的锁定:可以使用FileChannel的lock()和tryLock()方法对文件进行锁定,确保多个线程或进程之间对同一个文件的读写操作的互斥性。 总之,FileChannelNIO中用于处理文件的通道,它提供了一系列的API方法,可以方便地读取、写入和操作文件中的数据,同时还提供了一些文件的操作特性,如文件位置操作、通道之间的数据传输以及文件的锁定等。使用FileChannel可以更灵活地对文件进行操作,并且相比于传统的IO操作,它在性能上可以得到一定的提升。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值