传统的解决方法
  通常在Java中处理堵塞I/O要用到线程(大量的线程)。通常是实现一个线程池用来处理请求,如图二


  
图二

  线程使得server能够处理多个连接,可是它们也相同引发了很多问题。每一个线程拥有自己的栈空间并且占用一些CPU时间,耗费非常大,并且非常多时间是浪费在堵塞的I/O操作上,没有有效的利用CPU。
  三. 新I/O
  1. Buffer
  传统的I/O不断的浪费对象资源(一般是String)。新I/O通过使用Buffer读写数据避免了资源浪费。Buffer对象是线性的,有序的数据集合,它依据其类别仅仅包括的数据类型。
  java.nio.Buffer 类描写叙述
  java.nio.ByteBuffer 包括字节类型。 能够从ReadableByteChannel中读在 WritableByteChannel中写
  java.nio.MappedByteBuffer 包括字节类型,直接在内存某一区域映射
  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。特别的,你能够创建MappedBytesBuffer通过调用FileChannel.map(int mode,long position,int size)。直接(direct)buffer在内存中分配一段连续的块并使用本地?问方法读写数据。非直接(nondirect)buffer通过使用Java中的数组?问代码读写数据。有时候必须使用非直接缓冲比如使用不论什么的wrap方法(如ByteBuffer.wrap(byte[]))在Java数组基础上创建buffer。
  2. 字符编码
  向ByteBuffer中存放数据涉及到两个问题:字节的顺序和字符转换。ByteBuffer内部通过ByteOrder类处理了字节顺序问题,可是并没有处理字符转换。其实,ByteBuffer没有提供方法读写String。
  Java.nio.charset.Charset处理了字符转换问题。它通过构造CharsetEncoder和CharsetDecoder将字符序列转换成字节和逆转换。
  3. 通道(Channel)
  你可能注意到现有的java.io类中没有一个能够读写Buffer类型,所以NIO中提供了Channel类来读写Buffer。通道能够觉得是一种连接,能够是到特定设备,程序或者是网络的连接。通道的类等级结构图例如以下


  
图三

  图中ReadableByteChannel和WritableByteChannel分别用于读写。
  GatheringByteChannel能够从使用一次将多个Buffer中的数据写入通道,相反的,ScatteringByteChannel则能够一次将数据从通道读入多个Buffer中。你还能够设置通道使其为堵塞或非堵塞I/O操作服务。
  为了使通道可以同传统I/O类相容,Channel类提供了静态方法创建Stream或Reader
  4. Selector
  在过去的堵塞I/O中,我们一般知道什么时候能够向stream中读或写,由于方法调用直到stream准备好时返回。可是使用非堵塞通道,我们须要一些方法来知道什么时候通道准备好了。在NIO包中,设计Selector是为了这个目的。SelectableChannel能够注?特定的事件,而不是在事件发生时通知应用,通道跟踪事件。然后,当应用调用Selector上的随意一个selection方法时,它查看注?了的通道看是否有不论什么感兴趣的事件发生。图四是selector和两个已注?的通道的样例


  
图四

  并非全部的通道都支持全部的操作。SelectionKey类定义了全部可能的操作位,将要用两次。首先,当应用调用SelectableChannel.register(Selector sel,int op)方法注?通道时,它将所需操作作为第二个?数传递到方法中。然后,一旦SelectionKey被选中了,SelectionKey的readyOps()方法返回全部通道支持操作的数位的和。SelectableChannel的validOps方法返回每一个通道同意的操作。注?通道不支持的操作将引发IllegalArgumentException异常。下表列出了SelectableChannel子类所支持的操作。
  ServerSocketChannel OP_ACCEPT
  SocketChannel OP_CONNECT, OP_READ, OP_WRITE
  DatagramChannel OP_READ, OP_WRITE
  Pipe.SourceChannel OP_READ
  Pipe.SinkChannel OP_WRITE
  四. 举例说明
  1. 简单网页内容下载
  这个样例很easy,类SocketChannelReader使用SocketChannel来下载特定网页的HTML内容。
package examples.nio;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.net.InetSocketAddress;
import java.io.IOException;
public class SocketChannelReader{
private Charset charset=Charset.forName("UTF-8");//创建UTF-8字符集
private SocketChannel channel;
public void getHTMLContent(){
try{
connect();
sendRequest();
readResponse();
}catch(IOException e){
System.err.println(e.toString());
}finally{
if(channel!=null){
try{
channel.close();
}catch(IOException e){}
}
}
}
private void connect()throws IOException{//连接到CSDN
InetSocketAddress socketAddress=
new InetSocketAddress("http://www.csdn.net",80/);
channel=SocketChannel.open(socketAddress);
//使用工厂方法open创建一个channel并将它连接到指定地址上
//相当与SocketChannel.open().connect(socketAddress);调用
}
private void sendRequest()throws IOException{
channel.write(charset.encode("GET "
+"/document"
+" "));//发送GET请求到CSDN的文档中心
//使用channel.write方法,它须要CharByte类型的?数,使用
//Charset.encode(String)方法转换字符串。
}
private void readResponse()throws IOException{//读取应答
ByteBuffer buffer=ByteBuffer.allocate(1024);//创建1024字节的缓冲
while(channel.read(buffer)!=-1){
buffer.flip();//flip方法在读缓冲区字节操作之前调用。
System.out.println(charset.decode(buffer));
//使用Charset.decode方法将字节转换为字符串
buffer.clear();//清空缓冲
}
}
public static void main(String [] args){
new SocketChannelReader().getHTMLContent();
}