一、概念面向块的非阻塞IO系统。由通道、缓冲和Selector实现,通道传输数据,缓冲暂存和操作数据,Selector支持单线程操作多缓冲(1)优势:• NIO有缓冲功能,通过使用map方法可以直接将“一块数据”映射到内存中,比较高效。FileChannel的map方法返回MappedByteBuffer对象,将磁盘文件的部分或全部内容映射到内存中• NIO提供了支持非阻塞式IO的Selector类• NIO提供了将unicode字符串映射成字节序列及反映射的Charset类二、相关包java.nio Buffer 相关的类java.nio.channels Channel和Selector相关的类java.nio.charset 字符集相关的类java.nio.channels.spi 提供Channel服务相关的类java.nio.charset.spi 提供字符集服务相关的类三、Buffer(缓冲) 重要子类ByteBuffer MappedByteBufferCharBuffer ShortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer 本质是一个数组,程序用来操作数据 0<=mark<=position<=limit<=capacity• 容量(capacity):最大数据容量。创建后不可改变• 界限(limit):位于limit后的数据不可被读写• 位置(position):下一个可被读写的缓冲区位置索引• 标记(mark):允许将postion直接定位至此处 (1)方法int capacity() 返回capacity的大小int limit() 返回limit的大小int postition() 返回position的大小Buffer mark() 在当前postition处设置markBuffer limit(int newLt) 返回一个重新设置limit值的新BufferBuffer postition(int newPs) 返回一个重新设置postition值的新BufferBuffer reset() 将postition转到mark所在的位置Buffer rewind() postition置0,并取消mark,用于重读数据boolean hasRemain() 判断position和limit间是否还有可供处理的元素intt remaining() postition和limit间的元素个数Buffer flip() limt=position,position=0,丢弃标记,用于写切换到读Buffer clear() limit=capacity,posititon=0,丢弃标记,用于读切换到写使用Buffer读写数据的一般步骤:• 写入数据到Buffer• 调用flip()方法• 从Buffer中读取数据• 调用clear()方法或者compact()方法(2)Buffer的所有子类的额外方法• 读写Xxx get() 返回position位置的数据,position递增 相对XxxBuffer put(xxx c) 写入数据到position,position递增 相对Xxx get(index i) 返回i位置的数据,position不变 绝对XxxBuffer put(xxx c) 写入数据到i位置,position不变 绝对• static XxxBuffer allocate(int capacity) 创建普通Buffer对象o ByteBuffer特有的创建直接Buffer对象的方法:static ByteBuffer allocateDirect(int capacity) 更高效,绕过JVM• XxxBuffer compact()o 压缩缓冲区,将position和limit间的数据复制到缓冲区开始处,重置position为复制的字节数,limit为capacityboolean equals(Object ob) 判断此缓冲区是否与另一个对象相同。必须同时满足:(1)具有相同的元素类型和变量类型(2)具有相同数量的剩余元素(3)剩余元素序列(与它们的起始位置无关)逐点相同int compareTo(XxxBuffer that) 比较同类型的缓冲区。A小于B必须满足:(1)A第一个不相等的元素小于B中对应的元素(2)所有元素都相等,但A比B先耗尽(3)示例• 创建Buffer o ByteBuffer buf = CharBuffer.allocate(1024);• 向Buffer写数据,两种方法o int bytesRead = inChannel.read(buf);o buf.put((byte)127);• 从Buffer读数据,两种方法o int bytesWritten = inChannel.write(buf);o byte aByte = buf.get();四、Channel(通道)FileChannel 从文件中读写数据DatagramChannel 通过UDP读写网络中的数据SocketChannel 通过TCP读写网络中的数据ServerSocketChannel 监听新进来的TCP连接,像web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。Channel是一个java.nio.channels的接口,系统为该接口提供了各种实现类。全双工,比流能更好映射底层OS的API,特别是UNIX网络编程模型中底层OS的通道是全双工的4.1 与流的区别:• 程序不能直接读取Channel中的数据,需要通过Buffer对象访问。• 通道既能读数据,也能写数据,是双向的,而流是单向的。• 通道可以异步读写传统的节点流的getChannel方法可以创建对应的Channel对象,但SocketChannel由SocketChannel.open()创建,再使用connect方法连接。4.2 方法:MappedByteBuffer map(FileChannel.MapMode mode,long position, long size) 第一个参数是映射模式。用于实现NIO的块功能 FileChannelint read(ByteBuffer dst) 将字节序列从此通道中读入给定的缓冲区 SocketChannel、FileChannellong read(ByteBuffer[] dsts) 将字节序列从此通道scatter分发到给定的缓冲组 SocketChannel、FileChannellong read(ByteBuffer[] dsts, int offset, int length) 将字节序列从此通道读入给定缓冲组的子序列中 SocketChannel、FileChannelint write(ByteBuffer src) 将字节序列从给定的缓冲组中写入此通道 SocketChannel、FileChannellong write(ByteBuffer[] srcs) 将字节序列从给定的缓冲组gather聚集写入此通道 SocketChannel、FileChannellong write(ByteBuffer[] srcs, int offset, int length) 将字节序列从给定缓冲组的子序列写入此通道 SocketChannel、FileChannellong transferFrom(ReadableByteChannel src, long position, long count) 将字节从给定的可读取字节通道传输到此FileChannel通道的文件中 FileChannellong transferTo(long position, long count, WritableByteChannel target) 将字节从FileChannel通道的文件传输到给定的可写入字节通道 FileChannelFileChannel truncate(long size) 截取文件时,文件将中指定长度后面的部分将被删除 FileChannelvoid force(boolean metaData) 将通道里尚未写入磁盘的数据强制写到磁盘上,metaData指明是否同时将文件元数据(权限信息等)写到磁盘上 FileChannel4.3 常用Channel示例(1)FileChannel文件通道,不能运行在非阻塞模式下,不能注册到Selector• 打开FileChannelo FileChannel inChannel=new RandomAccessFile(f, "rw").getChannel();• 从FileChannel读取数据,两种方法o MappedByteBuffer mbb=inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length() );o 或ByteBuffer bb=ByteBuffer.allocate((int)f.length());int buffer=inChannel.read(bb);• 将读取的字节序列解码为GBK字符集的字符序列并输出,用Charset类o Charset charset=Charset.forName("GBK");CharsetDecoder decoder=charset.newDecoder();CharBuffer charBuffer=decoder.decode(bb);System.out.println(charBuffer);• 向FileChannel写入数据o bb.clear();bb.put(newData.getBytes());bb.flip();while(bb.hasRemaining()) {inChannel.write(bb);}五、Selector多路复用器允许单线程处理多个Channel,提供选择已经就绪的任务的能力,实现非阻塞的核心代码。可用来实现非阻塞式socket通信。要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。一个多路复用品可以同时轮询多个Channel,由于JDK使用了epoll()代替了传统的select实现,没有最大连接句柄1024/2048的限制,只需一个线程负责Selector的轮询,就可以接入成千上万的客户端 (1)Selector类static Selector open() 打开一个选择器int select() 阻塞到至少有一个通道在你注册的事件上就绪,返回自上次调用select()方法后有多少通道变成就绪状态int select(long timeout) 阻塞到至少有一个通道在你注册的事件上就绪,最长会阻塞timeout毫秒int selectNow() 不会阻塞,不管什么通道就绪都立刻返回,则无就绪则返回0Set
来源: