6.Pipe
Pipe是一个单向的管道,可以作为Java在JVM进程的线程间通讯的工具。在使用时,通信的线程共享一个Pipe,消息的发送方使用sink()方法获得一个通道,由这个通道写入数据。消息的接收方使用source()方法获得一个通道,然后又这个通道接收数据。
Pipe pipe = Pipe.open(); //获得pipe
pipe.sink().write(bb); //发送数据的线程
pipe.source().read(bb); //接收数据的线程
7.内存映射MappedByteBuffer
我们知道,在应用程序进行IO时,实际上其进程/线程是需要进行系统调用而发生阻塞的,系统调用会将文件或Socket中的数据拷贝到内核,再将内核中的数据拷贝到用户地址空间。在使用Java NIO时,对于大文件数据我们可以使用MappedByteBuffer,该类是ByteBuffer的子类,它可以将文件直接映射到操作系统的虚拟内存中,如果文件比较大则会分段映射。在映射完成之后,可以使用户空间与内核在处理这个文件的时候直接使用该虚拟内存的数据,也就是说不需要再进行数据拷贝。
对于I/O底层原理可以参考Java NIO预备知识:I/O底层原理与网络I/O模型
7.1 使用方法
使用FileChannel对象的map方法可以获得一个MappedByteBuffer对象,其参数为映射模式、对文件的映射开始地址以及ByteBuffer的大小。
映射模式为:
FileChannel.MapMode.READ_ONLY:缓冲区只读,对缓冲区的写操作将导致ReadOnlyBufferException
FileChannel.MapMode.READ_WRITE:缓冲区可写,对缓冲区的写操作将写回文件,但其他对该文件的映射不一定可以立即看到修改,这个确切行为依赖于操作系统。
FileChannel.MapMode.RPRIVATE:缓冲区可写,但对缓冲区的修改是私有的,不会传播到文件中。
FileChannel channel = FileChannel.open(Paths.get("file/Test.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE);
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());
1.由于需要构建缓冲区,所以对于中小文件来说,没有必要使用内存映射。
2.缓冲区不可关闭,只有JVM的垃圾回收才可以将其关闭,而这个过程是不确定的。
8.文件锁FileLock
与多线程访问一个共享变量类似,若多个程序对一个文件进行修改可能会对文件产生破坏。因此可以使用文件锁,控制对文件或文件摸个范围自己的访问。
8.1 加锁
lock与trylock方法,前者为试图获得锁,得不到则会阻塞线程,第二种方法在得不到时立即返回null。
这两个方法的无参数版本是锁住整个文件,带参数版本:
lock/tryLock(long start, long size, boolean shared)
前两个参数表示其实字节与字节数,第三个参数若为true表示锁是一个共享锁,允许多个进程获得该锁并从文件中读入,同时阻止获得独占锁。为false则是一个独占锁。
1.并非所有操作系统都支持共享锁,调用锁对象的isShared方法可以查询锁的类型。
2.若锁定的字节包括了文件的尾部,若这个文件的长度随后增长超过了锁定的部分,那么超出来的部分是没锁的,若想新增字节也可以锁定则可以使用Long.MAX_VALUE作为锁定字节数来保证。
8.2 释放锁:当FileChannel关闭是或者使用锁对象调用release方法/close方法可以释放锁。
1.文件加锁机制高度依赖于操作系统,在某些系统中,文件加锁仅仅是建议性的,即未得到锁仍旧可以向文件中执行写操作。在某些操作系统中不能在锁定一个文件的同时将其映射到内存中。在某些系统中,关闭一个通道将会释放JVM持有的底层文件的所有锁,因此尽量避免对于一个文件使用多个通道获得锁。
2.文件锁由整个JVM持有,若由一个JVM启动的不同程序试图获得同一个文件的锁将会抛出异常。
3.尽量不要在网络文件系统上锁定文件。
参考资料
Java NIO系列教程(十一) Pipe
小师妹学JavaIO之:MappedByteBuffer多大的文件我都装得下