Java是非常繁杂的语言, 比如IO就是典型的代表...
Java IO
首先1.0, 是基于8位字节流的InputStream和OutputStream系列
然后是1.1, 是基于16位的字符流(unicode)的Reader和Writer系列
下表是对应关系, 其中InputStreamReader和OutputStreamWriter, 起到两个系列之间的适配作用
当然实际的继承关系比这个复杂的多, 通过装饰模式, 产生各种IO类, 非常繁杂
Java NIO, New IO
从1.4开始, Java提供new IO
其实在new IO中主要两个提升
a, buffer和channel
解决小块数据传输带来的效率问题, 引入buffer数据结构, 可以批量传输以提高效率(这样更符合底层数据传输的方式), 一个挖煤的比喻, 从挖一铲运一铲到挖满一卡车再运出来
b, 对于socket channel加入非阻塞方式
Buffer
提供一种支持丰富操作的数据结构, 同时虽然对于所有的类型(除bool类型)都有相应的buffer, 但是只有ByteBuffer可以直接被channel读取, 其余的都需要在放到channel之前做类型转换
数据结构
支持操作
下图以FileChannel为例子,
首先Channel只能接收ByteBuffer作为write/read的参数
对于其他的buffer必须做类型转换, 尤其对于CharBuffer需要考虑charset的encode/decode
Channel
管道很形象, 就是连接发送端和接收端之间的媒介
其实就是对于传统socket或file接口针对ByteBuffer的封装
I/O可以分为广义的两大类别:File I/O和Stream I/O
所以对应的, Channel分为两类, FileChannel和Socket相关channel(SocketChannel、ServerSocketChannel和 DatagramChannel)
FileChannel
FileChannel类可以实现常用的read,write以及scatter/gather操作(对于多个buffer的批处理), 同时它也提供了很多专用于文件的新方法.
文件通道总是阻塞式的, 因为现代操作系统都有复杂的缓存和预取机制, 所以本地磁盘I/O操作延迟很少
FileChannel对象不能直接创建。一个FileChannel实例只能通过在一个打开的file对象(RandomAccessFile、FileInputStream或 FileOutputStream)上调用getChannel( )方法获取
FileChannel对象是线程安全(thread-safe)
1.4中, Java通过FileChannel实现了文件锁(之前Java不支持文件锁), 但是这是进程级别锁, 相同进程中不同线程无法通过文件锁进行互斥
SocketChannel
新的socket通道类可以运行非阻塞模式, 这个大大提升了Java IO的性能, 之前只能使用多线程阻塞的方式来处理并发, 但线程调度的开销也很高尤其当维护大量线程的时候
全部socket通道类(DatagramChannel、SocketChannel和ServerSocketChannel), 分别对应于java.net中的(Socket、ServerSocket和DatagramSocket), 并且Channel其实就是对他们的封装
ServerSocketChannel
ByteBuffer buffer = ByteBuffer.wrap (GREETING.getBytes( )); ServerSocketChannel ssc = ServerSocketChannel.open( ); ssc.socket( ).bind (new InetSocketAddress (port)); ssc.configureBlocking (false); //non-blocking while (true) { System.out.println ("Waiting for connections"); SocketChannel sc = ssc.accept( ); //不会blocking直接返回 if (sc == null) { // no connections, snooze a while Thread.sleep (2000); } else { System.out.println ("Incoming connection from: " + sc.socket().getRemoteSocketAddress( )); buffer.rewind( ); sc.write (buffer); sc.close( ); } }
SocketChannel
InetSocketAddress addr = new InetSocketAddress (host, port); SocketChannel sc = SocketChannel.open( ); sc.configureBlocking (false); //设置non-blocking sc.connect (addr); //不会阻塞等待 while ( ! sc.finishConnect( )) { doSomethingElse( ); } doSomethingWithChannel (sc); sc.close( );
从上面两个例子可以看出channel的使用, 其实和原来的API没有很大的不同, 关键就是支持non-blocking方式
Selector
当然还需要selector, 不然非阻塞意义不大, 象C/C++中的select, poll
Selector selector = Selector.open( );
channel1.register (selector, SelectionKey.OP_READ);
channel2.register (selector, SelectionKey.OP_WRITE);
channel3.register (selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// Wait up to 10 seconds for a channel to become ready
readyCount = selector.select (10000);
只有继承SelectableChannel的Channel类才可以被注册到Selector对象上, 所以FileChannel对象不是可选择的, 而所有SocketChannel都是可选择的
SelectionKey
代表了Selector和SelectableChannel的注册关系
key.attachment(); //返回SelectionKey的attachment,attachment可以在注册channel的时候指定
key.channel(); // 返回该SelectionKey对应的channel
key.selector(); // 返回该SelectionKey对应的Selector
key.interestOps(); //返回代表需要Selector监控的IO操作的bit mask
key.readyOps(); //返回一个bit mask,代表在相应channel上可以进行的IO操作
package java.nio.channels; //ops, selector所关心的通道操作,读(read),写(write),连接(connect)和接受(accept) public abstract SelectionKey register (Selector sel, int ops) throws ClosedChannelException; public abstract class SelectionKey { public static final int OP_READ; public static final int OP_WRITE; public static final int OP_CONNECT; public static final int OP_ACCEPT; public abstract SelectableChannel channel( ); public abstract Selector selector( ); public abstract void cancel( ); public abstract boolean isValid( ); public abstract int interestOps( ); public abstract void interestOps (int ops); public abstract int readyOps( ); public final boolean isReadable( ); public final boolean isWritable( ); public final boolean isConnectable( ); public final boolean isAcceptable( ); public final Object attach (Object ob) public final Object attachment( ) }
使用的代码
// Allocate an unbound server socket channel ServerSocketChannel serverChannel = ServerSocketChannel.open(); // Get the associated ServerSocket to bind it with ServerSocket serverSocket = serverChannel.socket(); // Create a new Selector for use below Selector selector = Selector.open(); // Set the port the server channel will listen to serverSocket.bind(new InetSocketAddress(port)); // Set nonblocking mode for the listening socket serverChannel.configureBlocking(false); // Register the ServerSocketChannel with the Selector serverChannel.register(selector, SelectionKey.OP_ACCEPT); //关注accept while (true) { // This may block for a long time. Upon returning, the // selected set contains keys of the ready channels. int n = selector.select(); if (n == 0) { continue; // nothing to do }
// Get an iterator over the set of selected keys Iterator it = selector.selectedKeys().iterator(); // Look at each key in the selected set while (it.hasNext()) { SelectionKey key = (SelectionKey) it.next(); // Is a new connection coming in? if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel channel = server.accept(); registerChannel(selector, channel, SelectionKey.OP_READ); //Accept后设置成关注Read sayHello(channel); } // Is there data to read on this channel? if (key.isReadable()) { readDataFromSocket(key); } } } }
本文章摘自博客园,原文发布日期:2013-10-10