NIO
New IO, 相对于IO基于字节方式读取流中的数据,NIO以块为单位从Channel读写,相对效率更高。
NIO分为File NIO和NetWork NIO。
File NIO
File NIO通过FileChannel读写数据,FileChannel只能通过FileInputStrem/FileOutputStream获取
public FileChannel FileInputStrem:getChannel();
public FileChannel FileOutputStrem:getChannel();
FileChannel定义了通过ByteBuffer读写数据的接口:
//返回读取数据的字节数,-1代表数据结束
public int read(ByteBuffer dst) throws IOException;
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
public final long read(ByteBuffer[] dsts) throws IOException;
public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
//返回写入的数据字节数
public int write(ByteBuffer src) throws IOException;
public abstract int write(ByteBuffer src, long position) throws IOException;
public final long write(ByteBuffer[] srcs);
public abstract long write(ByteBuffer[] srcs, int offset, int length);
ByteBuffer是对字节块的抽象,内部是一个byte[], 提供了对块进行操作的各种方法。
FileChannel读取数据时,需先定义一个ByteBuffer用于接受数据,同理写数据时也该先将数据写入ByteBuffer.ByteBuffer提供了静态方法分配一个新ByteBuffer.
public static ByteBuffer allocate(int capacity);
//向Buffer存数据,可指定存放位置index,putShort/putInt/putLong/putFloat/putDouble
public ByteBuffer put(byte b);
public ByteBuffer put(byte[] src);
public ByteBuffer putChar(char value);
public ByteBuffer putChar(int index, char value);
.....
//从Buffer获取数据,可指定位置index, getShort/getInt/getLong/getFloat/getDouble
public byte get();
public byte get(int index);
public char getChar();
public char getChar(int index);
//flip方法让buffer处于初始可读状态
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
NetWork NIO
NetWork NIO通过SocketChannel读写数据,UDP中直接new SocketChannel绑定到某个SocketAddress,TCP则需要通过ServerSocketChannel监听到某个端口后接受连接产生一个SocketChannel。
SocketChannel同FileChannel一样,实现了ByteChannel接口,提供读写ByteBuffer的方法相同。
ServerSocketChannel通过静态方法open直接获取,ServerSocketChannel通过accept方法接受一个连接,不能进行读写数据:
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
一般IO是阻塞操作,这样单线程如果发生IO阻塞就无法处理其他请求,吞吐量较低,异步IO可以让单线程处理多个IO事件,通过在Selector上注册多个异步Channel监听Channel上发生的IO事件,有点类似Linux下的epoll.
Selector静态方法open()新建Selector:
//open新建Selector
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
//阻塞监听注册的SelectableChannel是否有IO事件发生,如有IO发生,则返回发生IO的Channel个数
public int select() throws IOException;
public int select(long timeout) throws IOException;
//获取发生IO事件的SelectionKey
public Set<SelectionKey> selectedKeys();
//关闭注册的SelectionKey,但Channel本身并不会关闭
public void close() throws IOException;
继承自SelectableChannel接口的类可向Selector注册,SocketChannel/ServerSocketChannel都继承了SelectableChannel,注册前需设置Chennel为非阻塞模式,注册到Selector后返回SelectionKey即代表了注册的Channel,通过SelectorKey可获取Channel上发生的事件。
public SelectableChannel configureBlocking(boolean block) throws IOException;
public SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException;
public SelectionKey register(Selector sel, int ops) throws ClosedChannelException;