@lemonguge
2015-07-03T08:23:53.000000Z
字数 6630
阅读 452
NIO
在之前介绍了关于Socket流的通道和文件通道,这里将介绍nio的管道Pipe。查看API可以发现该类只有有三个方法,不过应该注意到,它具有两个嵌套类:Pipe.SinkChannel和Pipe.SourceChannel。
Pipe.SinkChannel可写入的通道,实现了聚集写入GatheringByteChannel和从缓冲区写入通道的接口WritableByteChannelPipe.SourceChannel可读取的通道,实现了分散读取ReadableByteChannel和从通道读取到缓冲区的接口ReadableByteChannel简单的说,数据会被写到sink 通道,从source 通道读取,以下是Pipe原理的图示:

现在来明确使用管道的基本思路,首先我们需要创建管道,通过Pipe的静态open()方法打开管道。
向管道写入数据,需要访问Pipe.SinkChannel,通过管道的sink()获取可写入管道。我们可以通过该管道的write()方法将缓冲区的数据写入。
从管道读取数据,需要访问Pipe.SourceChannel,通过管道的source()获取可读取管道。可以通过该管道的read()方法将数据读取到缓冲区。当该通道已到达流的末尾,则返回-1。
以下是一个完整的管道示例:
import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.Pipe;public class PipeDemo {public static void main(String[] args) throws IOException {int length = 1024;Pipe pipe = Pipe.open();Pipe.SinkChannel sink = pipe.sink();Pipe.SourceChannel source = pipe.source();ByteBuffer buf = ByteBuffer.wrap("Hello World!".getBytes());while (buf.hasRemaining())sink.write(buf);// 如果不关闭可写入管道,可读取管道的read方法会一直阻塞,不会返回-1sink.close();buf = ByteBuffer.allocate(length);byte[] dst = new byte[length];int size = 0;while ((size = source.read(buf)) != -1) {buf.flip();buf.get(dst, 0, size);System.out.println(new String(dst, 0, size));buf.clear();}source.close();}} /* Output:Hello World!*///:~
文件锁就像是同步时的对象锁,它们是劝告式(advisory)的锁。劝告式的锁又称询问式的锁,这种类型的锁将拥有互斥的效果。锁的效果与操作系统有关,锁还有另一种效果,强制性(mandatory),当某个进程获得锁后,操作系统将保证其他进程无法操作共享资源。
对于文件锁FileLock的两种不同的锁定方式,可以文件锁分为两种:
通过查看关于FileChannel文件通道的API,发现有四种获取锁的方式:
FileLock lock()将获得此通道的文件的独占锁;FileLock lock(long position, long size, boolean shared)可以指定文件锁定区域的开始位置、大小和锁的类型,shared为true的时候,获得共享锁,为false时获得独占锁;FileLock tryLock()试图获得此通道的文件的独占锁,当无法获取锁时放回null;FileLock tryLock(long position, long size, boolean shared)试图获取此通道的文件的指定区域的锁lock()方法为阻塞式方法、tryLock()方法是非阻塞的方法,如果想通过tryLock()获取锁,可以放在do..while..进行循环判断。当拥有锁后,可以调用FileLock对象的release()释放锁,尝试获得锁的其他任何线程都有机会获得它。
以下是一个关于文件锁的示例:
import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.RandomAccessFile;import java.nio.channels.FileChannel;import java.nio.channels.FileLock;class LockFile implements Runnable {private FileLock lock;private RandomAccessFile raf;public LockFile(FileLock lock, RandomAccessFile raf) {this.lock = lock;this.raf = raf;}@Overridepublic void run() {try {// 判断锁的类型,true为共享锁System.out.println(lock.isShared() ? "共享锁" : "独占锁");Thread.sleep(3000);System.out.println("释放锁");// 释放锁lock.release();raf.close();} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}}// 普通的文件读取class FileRead implements Runnable {private File file;public FileRead(File file) {this.file = file;}@Overridepublic void run() {try {System.out.println("读取文件");FileInputStream fis = new FileInputStream(file);BufferedReader br = new BufferedReader(new InputStreamReader(fis));String line = null;while ((line = br.readLine()) != null)System.out.println(line);br.close();} catch (IOException e) {e.printStackTrace();}}}public class FileLockDemo {public static void main(String[] args) throws InterruptedException, IOException {// 在当前项目下有file.txt文件,文件有两行内容// 分别为“abc123”和“你好”File file = new File("file.txt");RandomAccessFile raf = new RandomAccessFile(file, "rw");// 查看传入共享锁和独占锁的输出结果new Thread(new LockFile(getLock(raf, false), raf)).start();// 当传入独占锁时,设置睡眠时间为1000和4000的输出结果Thread.sleep(1000);new Thread(new FileRead(file)).start();}// 设置isShared为true将获得共享锁、false获得独占锁private static FileLock getLock(RandomAccessFile raf, boolean isShared) throws IOException {FileChannel fileChnl = raf.getChannel();if (isShared)return fileChnl.lock(0, fileChnl.size(), true); // 同样锁定整个文件else// 获取独占锁,必须为可写入通道return fileChnl.lock();}} /* Output:独占锁读取文件java.io.IOException: 另一个程序已锁定文件的一部分,进程无法访问。at java.io.FileInputStream.readBytes(Native Method)at java.io.FileInputStream.read(FileInputStream.java:272)at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)at java.io.InputStreamReader.read(InputStreamReader.java:184)at java.io.BufferedReader.fill(BufferedReader.java:154)at java.io.BufferedReader.readLine(BufferedReader.java:317)at java.io.BufferedReader.readLine(BufferedReader.java:382)at nio.FileRead.run(FileLockDemo.java:50)at java.lang.Thread.run(Thread.java:744)释放锁*///:~
当传入共享锁,睡眠时间为1000ms时,会显示以下输出结果:
共享锁读取文件abc123你好释放锁
当传入独占锁,睡眠时间为4000ms时,则会显示以下输出结果:
独占锁释放锁读取文件abc123你好
如果获取一个独占锁,那么其他人就不能获得同一个文件或者文件的一部分上的锁。如果获得一个共享锁,那么其他人可以获得同一个文件或者文件一部分上的共享锁,但是不能获得独占锁。
以下是一个线程获得了共享锁后,另一个线程想获得独占锁的示例:
import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.RandomAccessFile;import java.nio.channels.FileLock;class ShareLock implements Runnable {private File file;public ShareLock(File file) {this.file = file;}@Overridepublic void run() {FileInputStream fis;try {fis = new FileInputStream(file);System.out.println("获取共享锁");FileLock lock = fis.getChannel().lock(0, fis.available(), true);Thread.sleep(3000);lock.release();System.out.println("释放共享锁");} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}}public class SharedLockDemo {public static void main(String[] args) throws IOException, InterruptedException {File file = new File("file.txt");new Thread(new ShareLock(file)).start();RandomAccessFile raf = new RandomAccessFile(file, "rw");// 保证会先获得共享锁Thread.sleep(1000);System.out.println("获取独占锁");// 获得独占锁raf.getChannel().lock();// 关闭文件通道,也会使FileLock的生命周期结束raf.close();}} /* Output:获取共享锁获取独占锁Exception in thread "main" java.nio.channels.OverlappingFileLockExceptionat sun.nio.ch.SharedFileLockTable.checkList(FileLockTable.java:255)at sun.nio.ch.SharedFileLockTable.add(FileLockTable.java:152)at sun.nio.ch.FileChannelImpl.lock(FileChannelImpl.java:1011)at java.nio.channels.FileChannel.lock(FileChannel.java:1052)at nio.SharedLockDemo.main(SharedLockDemo.java:44)释放共享锁*///:~
文件锁定并不总是出于保护数据的目的,可能临时锁定一个文件以保证特定的写操作成为原子的,而不会有其他程序的干扰。
在我的Java I/O系列的第三篇文章中转换流部分,曾经提到nio中的字符集Charset类,当时通过该类获得系统的默认编码。接下来我要讲述的类都在java.nio.charset包中,查看API可以发现是这么描述字符集的:
16位的Unicode代码单元序列(字符序列)和字节序列之间的指定映射关系。
Java语言被定义为基于Unicode,即使你把类名写成中文也同样能运行。
public class 你好 {public static void main(String[] args) {System.out.println("你好");}} /* Output:你好*///:~
在Java I/O中提到过解码与编码,转换流InputStreamReader和OutputStreamWriter就分别对应着解码和编码。不过在Java的一切都是对象的思想下,编码和解码也变成了对象,我们把编码称为CharsetEncoder编码器,解码称为CharsetDecoder解码器。简单的来说,解码用于读取,编码用于写入。
我们可以通过字符集Charset类的静态forName()方法获取指定字符集名称的字符集。
通过此字符集,可以通过newDecoder()创建一个解码器,通过newEncoder()方法创建一个编码器。
可以通过CharsetDecoder解码器的decode()方法将字节缓冲区解码到新分配的字符缓冲区,然后我们可以处理字符了。
如果想要写好数据,必须使用CharsetEncoder编码器的encode()方法将字符缓冲区编码到新分配的字节缓冲区。
解码器和编码器一般可以和标准I/O流结合使用,即在构造转换流时当作参数传入。
后记:nio库有大量的特性,它为我们最熟悉的标准I/O过程也带来了新的活力,通道和缓冲区可以做的事情几乎都可以用原来的面向流的类来完成。但是通道和缓冲区可以以接近系统所允许的最大速度完成这些相同的旧操作。知识点挺多的,不过终于写完啦,写于2015年7月3日。