NIO和IO
NIO的四个关键数据类型
- Buffer:它包含数据且用于读写的线性表结构,还提供一个特殊类用于内存映射的I/O操作。
- Charset:提供Unicode字符串映射到字节序列以及逆映射的操作。
- Channels:包含socket,file和pip三种,是一种双向交通的通道。
- Selectors:将多元异步I/O操作集中到一个或多个线程中(类似于linux的select函数)
传统IO
服务端:
1ServerSocket server = new ServerSocket(1000); 2Socket conn = server.accept(); 3InputStream in = conn.getInputStream(); 4InputStreamReader reader = new BufferedReader(reader); 5Request request = new Request(); 6while(!request.isComplete()){ 7 String line = reader.readLine(); 8 request.addLine(line); 9}
上述操作有两个问题:
- BufferedReader类的readLine() 方法在其缓冲未满时,会一直阻塞,只有一定的数据填满缓冲或者client关闭连接,此方法才能返回。
- BufferedReader会产生大量的垃圾需要GC。BufferedReader需要创建缓冲区来从client读取数据,但是同样创建了一些字符串存储这些数据。
BufferedReader默认有$2^{13}$次方字符的缓冲大小。
类似的,在处理写操作时,一次写入一个字符,效率很低,也需要使用缓冲写,但是这也会插死你横更多的垃圾。
在传统的I/O中要使用大量的线程,通常使用线程池实现来处理请求。 但是即使这样,还是会有很多时间阻塞在I/O上,没有有效的利用CPU
NIO
Buffer
传统的I/O使用String来操作,浪费资源,新I/O通过使用Buffer读写数据避免浪费。
Buffer对象是线性的,有序的数据集合,他根据其类别只包含唯一的数据类型。
java.nio.Buffer: 类描述java.nio.ByteBuffer:字节类型。可以从ReadableByteChannel中读,在WritableByteChannel中写java.nio.CharBuffer:字符类型,不能写入通道java.nio.DoubleBuffer:double类型,不能写入通道java.nio.FloatBuffer:float类型java.nio.IntBuffer:int类型java.nio.LongBuffer:long类型java.nio.ShortBuffer:short类型
可以使用allocate(int capacity)方法或者allocateDirect(int capacity) 方法分配一个Buffer。
特别的,可以通过调用FileChannel.map(int mode, long position, int size)创建MappedByteBuffer。
Direct Buffer 在内存中分配一段连续的块并使用本地访问方法读写数据。non direct Buffer用过java中的数组读写数据。
有时间必须使用非直接的缓冲,例如使用任何wrap方法(如ButeBuffer.wrap(byte[]))在java数据自出上创建buffer。
字符编码
向ByteBuffer中存放数据涉及两个问题:字节的顺序和字符转换。ByteBuffer内部通过ByteOrder类处理了字节顺序问题,但是并未解决字符转换的问题。ByteBuffer没有提供方法读写String。
java.nio.charset.Charset处理字符转换的问题。通过构造CharsetEncoder和CharsetDecoder将字符序列转为字节和逆转换。
通道
java.io类中没有一个类可以读写Buffer类型,nio提供Channel读写Buffer。channel可以认为是一种连接,可以使到特定的设备,程序或者是网络。 channel类的等级结构如下: Channel(interface)->ReadableByteChannel(interface)->ScatteringByteChannel(interface) Channel(interface)->WritableByteChannel(interface)->GatherByteChannel(interface)
ByteChannel(interface)继承自: ReadableByteChannel(interface) WritableByteChannel(interface)
GatherByteChannel可以一次将多个Buffer中的数据写入通道,相反的ScatteringByteChannel可以一次将数据从通道中读入多个buffer中。还可以设置通道使其为阻塞或非阻塞I/O操作服务。
为了使通道与传统I/O兼容,Channel提供了静态的Stream或Reader。
Selector
在过去的阻塞I/O中,我们一般知道什么时候可以向stream中读或写,因为方法调用直到stream准备好时返回。但是使用非阻塞通道,我们需要一些方法来知道什么时候通道准备好了。在NIO包中,设计Selector就是为了这个目的。
SelectableChannel可以注册特定的事件,而不是在事件发生时通知应用,通道跟踪事件。然后,当应用调用Selector上的任意一个selection方法时,它查看注册了的通道看是否有任何感兴趣的事件发生。

并不是所有的通道都支持所有的操作。SelectionKey类定义了所有可能的操作位,将要用两次。
- 当应用调用
SelectableChannel.register(Selector sel,int op)方法注册通道时,它将所需操作作为第二个参数传递到方法中。 - 一旦
SelectionKey被选中了,SelectionKey的readyOps()方法返回所有通道支持操作的位数的和。SelectableChannel的validOps方法返回每个通道允许的操作。
注册通道不支持的操作将引发
IllegalArgumentException异常.
SelectableChannel子类支持的操作:
1ServerSocketChannel OP_ACCEPT 2SocketChannel OP_CONNECT, OP_READ, OP_WRITE 3DatagramChannel OP_READ, OP_WRITE 4Pipe.SourceChannel OP_READ 5Pipe.SinkChannel OP_WRITE
例子
- 简单网页内容下载
- 简单加法服务器和客户端
- 非阻塞加法服务器
简单网页下载
1import java.io.IOException; 2import java.net.InetSocketAddress; 3import java.nio.ByteBuffer; 4import java.nio.channels.SocketChannel; 5import java.nio.charset.Charset; 6 7public class WebDownload { 8 9 private final static Charset charset = Charset.forName("UTF-8"); 10 private SocketChannel clientChannel; 11 12 public void download() { 13 connect(); 14 sendRequest(); 15 readResponse(); 16 } 17 18 //发送GET请求到CSDN的文档中心 19 private void sendRequest() { 20 //使用channel.write方法,它需要CharByte类型的参数,使用 21 //Charset.encode(String)方法转换字符串。 22 try { 23 clientChannel.write(charset.encode("GET / HTTP/1.1\r\n\r\n")); 24 } catch (IOException e) { 25 e.printStackTrace(); 26 } 27 } 28 29 private void readResponse(){ 30 ByteBuffer buff = ByteBuffer.allocate(1024);//创建1024字节的缓冲 31 32 try { 33 // -1 if the channel has reached end-of-stream 34 while(clientChannel.read(buff)!=-1){ 35 buff.flip();//flip方法在读缓冲区字节操作之前调用。 36 37 System.out.println(charset.decode(buff)); 38 39 buff.clear(); 40 } 41 } catch (IOException e) { 42 e.printStackTrace(); 43 } 44 } 45 46 private boolean connect() { 47 InetSocketAddress socketAddr = new InetSocketAddress("www.baidu.com", 80); 48 try { 49 clientChannel = SocketChannel.open(); 50 clientChannel.connect(socketAddr); 51 return true; 52 } catch (IOException e) { 53 e.printStackTrace(); 54 } 55 56 return false; 57 } 58 59 public static void main(String[] args) { 60 new WebDownload().download(); 61 } 62 63} 64
简单加法服务器和客户端
server端
1import java.io.IOException; 2import java.net.InetSocketAddress; 3import java.nio.ByteBuffer; 4import java.nio.IntBuffer; 5import java.nio.channels.ServerSocketChannel; 6import java.nio.channels.SocketChannel; 7 8 9public class AddServer { 10 11 private ServerSocketChannel server = null; 12 private SocketChannel client = null; 13 private ByteBuffer buff = ByteBuffer.allocate(8); 14 private IntBuffer intBuff = buff.asIntBuffer(); 15 16 public void connect(){ 17 try { 18 server = ServerSocketChannel.open(); 19 server.bind(new InetSocketAddress(80)); 20 System.out.println("channel open!"); 21 } catch (IOException e) { 22 e.printStackTrace(); 23 } 24 } 25 26 27 public void waitForConnection(){ 28 try { 29 client = server.accept(); 30 if(client!=null){ 31 System.out.println("client connect!"); 32 processRequest(); 33 } 34 } catch (IOException e) { 35 e.printStackTrace(); 36 } 37 } 38 39 public void processRequest(){ 40 buff.clear(); 41 try { 42 client.read(buff); 43 int result = intBuff.get(0)+intBuff.get(1); 44 buff.flip(); 45 buff.clear(); 46 intBuff.put(0, result); 47 client.write(buff); 48 } catch (IOException e) { 49 e.printStackTrace(); 50 } 51 52 } 53 54 public void run(){ 55 this.connect(); 56 this.waitForConnection(); 57 this.processRequest(); 58 } 59 /** 60 * [@param](http://my.oschina.net/u/2303379) args 61 */ 62 public static void main(String[] args) { 63 new AddServer().run(); 64 } 65 66} 67
client 端
1import java.io.IOException; 2import java.net.InetSocketAddress; 3import java.nio.ByteBuffer; 4import java.nio.IntBuffer; 5import java.nio.channels.SocketChannel; 6 7public class AddClient { 8 private SocketChannel client = null; 9 private ByteBuffer buff = ByteBuffer.allocate(8); 10 private IntBuffer intBuff = buff.asIntBuffer(); 11 12 public void connect() { 13 try { 14 client = SocketChannel.open(); 15 client.connect(new InetSocketAddress("localhost", 80)); 16 } catch (IOException e) { 17 e.printStackTrace(); 18 } 19 20 } 21 22 public void request(int a, int b) { 23 buff.clear(); 24 intBuff.put(0, a); 25 intBuff.put(1, b); 26 try { 27 client.write(buff); 28 System.out.println("send request :" + a + "+" + b); 29 } catch (IOException e) { 30 e.printStackTrace(); 31 } 32 33 } 34 35 public int getresult() { 36 buff.clear(); 37 int result = 0; 38 try { 39 client.read(buff); 40 result = buff.getInt(0); 41 } catch (IOException e) { 42 e.printStackTrace(); 43 } 44 finally{ 45 try { 46 client.close(); 47 } catch (IOException e) { 48 e.printStackTrace(); 49 } 50 } 51 52 return result; 53 54 } 55 56 public int start(int a, int b){ 57 this.connect(); 58 this.request(a, b); 59 return this.getresult(); 60 } 61 62 /** 63 * [@param](http://my.oschina.net/u/2303379) args 64 */ 65 public static void main(String[] args) { 66 System.out.println(new AddClient().start(123, 345)); 67 } 68 69} 70
非阻塞加法服务器
1import java.io.IOException; 2import java.net.InetSocketAddress; 3import java.nio.ByteBuffer; 4import java.nio.IntBuffer; 5import java.nio.channels.SelectionKey; 6import java.nio.channels.Selector; 7import java.nio.channels.ServerSocketChannel; 8import java.nio.channels.SocketChannel; 9import java.nio.channels.spi.SelectorProvider; 10import java.util.Iterator; 11import java.util.Set; 12 13 14public class AddServer { 15 16 private ServerSocketChannel server = null; 17 private SocketChannel client = null; 18 private ByteBuffer buff = ByteBuffer.allocate(8); 19 private IntBuffer intBuff = buff.asIntBuffer(); 20 21 public void connect(){ 22 try { 23 server = ServerSocketChannel.open(); 24 server.bind(new InetSocketAddress(80)); 25 server.configureBlocking(false); 26 System.out.println("channel open!"); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 } 31 32 33 public void waitForConnection(){ 34 Selector acceptSelector; 35 try { 36 acceptSelector = SelectorProvider.provider().openSelector(); 37 SelectionKey acceptKey = server.register(acceptSelector, SelectionKey.OP_ACCEPT); 38 int keyadded = 0; 39 40 while( (keyadded = acceptSelector.select()) > 0){ 41 Set readyKeys = acceptSelector.selectedKeys(); 42 Iterator it = readyKeys.iterator(); 43 44 while(it.hasNext()){ 45 SelectionKey sk = (SelectionKey)it.next(); 46 it.remove(); 47 ServerSocketChannel nextReaedy = (ServerSocketChannel)sk.channel(); 48 client = nextReaedy.accept(); 49 processRequest(); 50 } 51 } 52 53 } catch (IOException e1) { 54 e1.printStackTrace(); 55 } 56 57 } 58 59 public void processRequest(){ 60 buff.clear(); 61 try { 62 client.read(buff); 63 int result = intBuff.get(0)+intBuff.get(1); 64 buff.flip(); 65 buff.clear(); 66 intBuff.put(0, result); 67 client.write(buff); 68 } catch (IOException e) { 69 e.printStackTrace(); 70 } 71 72 } 73 74 public void run(){ 75 this.connect(); 76 this.waitForConnection(); 77 this.processRequest(); 78 } 79 /** 80 * [@param](http://my.oschina.net/u/2303379) args 81 */ 82 public static void main(String[] args) { 83 new AddServer().run(); 84 } 85 86} 87
非阻塞的加法服务器首先通过SelectorProvider工厂方法建立选择器
acceptSelector = SelectorProvider.provider().openSelector();
然后在ServerSocketChannel上注册选择器和对应的事件。
SelectionKey acceptKey = server.register(acceptSelector, SelectionKey.OP_ACCEPT);
通过选择器获取当前是否有client连接到server:
acceptSelector.select()>0
然后将有client链接到server,获取成功连接的迭代器。遍历已经准备好的连接,分别处理请求。