Java Java 一文了解什么是NIO Moxy 2023-09-07 2023-09-08 认识Java中的NIO
nio(non-blocking io)非阻塞IO,在jdk1.4后加入,也有称为new io
1 三大组件 1.1 Channel和Buffer
Channel(通道)是双向的数据通道,比如InputStream和OutputStream是写入和写出数据,是单向的,而channel既可以输入数据也可以输出数据。
Buffer(缓冲区)用来暂存channel的数据,或者写出数据时,也是需要写到buffer然后写出通道
1.1.1 常见的Channel和Buffer
Channel
FileChannel:文件的数据传输通道
DatagramChannel:UDP网络数据传输通道
SocketChannel:TCP数据传输通道
ServerSocketChannel:TCP数据传输通道(用于服务器端)
Buffer
ByteBuffer
MappedByteBuffer
DirectByteBuffer
HeapByteBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
CharBuffer
1.2 Selector
Selectord的作用就是配合一个线程去管理多个通道,获取到channel的事件以后,通知线程来处理。
我们可以通过三个版本的设计来更加的清晰的了解Selector
多线程版:相当于一个线程和一个socket连接一一对应,把socket比做客人的话,那线程就是服务员
缺点:如果客流量大的话,那么需要的服务员太多,相当于一个可以一个服务员,很明显我们饭店时放不下这么多服务员
线程池版:那么上面的服务员多了,我们可以使用线程池来进行管理,相当于不管多少个客人,我们只有固定工位的服务员,这样就解决了上面的缺点
缺点:一个服务员还是会负责一个客人,即使客人什么的不做,也会等待客人走了以后才会服务下一个客人,我们可以称之为==阻塞式==,适合像http请求的短连接
Selector版:那么基于上面的线程池版,我们selector就相当于时给每个服务员的一个工具,可以第一时间感知到客人事件的工具,那么我们的客人也是进行了升级成了==channel==,我们的客人是工作早非阻塞模式下,不会让我们的服务员吊死在一个客人身上
2 ByteBuffer 2.1 使用ByteBuffer
ByteBuffer的使用是和Channel息息相关的,下面是使用ByteBuffer读取数据的步骤
获取Channel
分配缓冲区==ByteBuffer.allocate(10)==
循环读channel,直到返回-1,标识读完
在每次循环中,读取到BytenBuffer中就需要写出来,如下
切换ByteBuffer为写读模式==buffer.flip()==
调用get方法循环写出,可以使用==buffer.hasRemaining()==来判断是否写完了
切换ByteBuffer为写模式==buffer.clear()或buffer.compact()==
具体案例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public static void main (String[] args) { try (FileChannel channel = new FileInputStream ("data.txt" ).getChannel()) { ByteBuffer buffer = ByteBuffer.allocate(10 ); int readLen = channel.read(buffer); while (readLen != -1 ) { log.debug("读取到的字节数{}" , readLen); buffer.flip(); while (buffer.hasRemaining()) { byte b = buffer.get(); System.out.println((char ) b); } buffer.clear(); readLen = channel.read(buffer); } } catch (IOException e) { log.error("读取失败" ,e); } }
2.2 ByteBuffer结构 重要属性
capacity:容量,ByteBuffer可以容纳多少数据
positioon:读写指针,读到哪了,写到那了
limit:读写限制
执行时结构变化
读取4个字节后
clear发生后
compact方法
2.3 ByteBuffer方法 2.3.1 ByteBuffer工具类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 public class ByteBufferUtils { private static final char [] BYTE2CHAR = new char [256 ]; private static final char [] HEXDUMP_TABLE = new char [256 * 4 ]; private static final String[] HEXPADDING = new String [16 ]; private static final String[] HEXDUMP_ROWPREFIXES = new String [65536 >>> 4 ]; private static final String[] BYTE2HEX = new String [256 ]; private static final String[] BYTEPADDING = new String [16 ]; static { final char [] DIGITS = "0123456789abcdef" .toCharArray(); for (int i = 0 ; i < 256 ; i++) { HEXDUMP_TABLE[i << 1 ] = DIGITS[i >>> 4 & 0x0F ]; HEXDUMP_TABLE[(i << 1 ) + 1 ] = DIGITS[i & 0x0F ]; } int i; for (i = 0 ; i < HEXPADDING.length; i++) { int padding = HEXPADDING.length - i; StringBuilder buf = new StringBuilder (padding * 3 ); for (int j = 0 ; j < padding; j++) { buf.append(" " ); } HEXPADDING[i] = buf.toString(); } for (i = 0 ; i < HEXDUMP_ROWPREFIXES.length; i++) { StringBuilder buf = new StringBuilder (12 ); buf.append(StringUtil.NEWLINE); buf.append(Long.toHexString((long ) i << 4 & 0xFFFFFFFFL | 0x100000000L )); buf.setCharAt(buf.length() - 9 , '|' ); buf.append('|' ); HEXDUMP_ROWPREFIXES[i] = buf.toString(); } for (i = 0 ; i < BYTE2HEX.length; i++) { BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i); } for (i = 0 ; i < BYTEPADDING.length; i++) { int padding = BYTEPADDING.length - i; StringBuilder buf = new StringBuilder (padding); for (int j = 0 ; j < padding; j++) { buf.append(' ' ); } BYTEPADDING[i] = buf.toString(); } for (i = 0 ; i < BYTE2CHAR.length; i++) { if (i <= 0x1f || i >= 0x7f ) { BYTE2CHAR[i] = '.' ; } else { BYTE2CHAR[i] = (char ) i; } } } public static void debugAll (ByteBuffer buffer) { int oldlimit = buffer.limit(); buffer.limit(buffer.capacity()); StringBuilder origin = new StringBuilder (256 ); appendPrettyHexDump(origin, buffer, 0 , buffer.capacity()); System.out.println("+--------+-------------------- all ------------------------+----------------+" ); System.out.printf("position: [%d], limit: [%d]\n" , buffer.position(), oldlimit); System.out.println(origin); buffer.limit(oldlimit); } public static void debugRead (ByteBuffer buffer) { StringBuilder builder = new StringBuilder (256 ); appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position()); System.out.println("+--------+-------------------- read -----------------------+----------------+" ); System.out.printf("position: [%d], limit: [%d]\n" , buffer.position(), buffer.limit()); System.out.println(builder); } private static void appendPrettyHexDump (StringBuilder dump, ByteBuffer buf, int offset, int length) { if (MathUtil.isOutOfBounds(offset, length, buf.capacity())) { throw new IndexOutOfBoundsException ( "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length + ") <= " + "buf.capacity(" + buf.capacity() + ')' ); } if (length == 0 ) { return ; } dump.append( " +-------------------------------------------------+" + StringUtil.NEWLINE + " | 0 1 2 3 4 5 6 7 8 9 a b c d e f |" + StringUtil.NEWLINE + "+--------+-------------------------------------------------+----------------+" ); final int startIndex = offset; final int fullRows = length >>> 4 ; final int remainder = length & 0xF ; for (int row = 0 ; row < fullRows; row++) { int rowStartIndex = (row << 4 ) + startIndex; appendHexDumpRowPrefix(dump, row, rowStartIndex); int rowEndIndex = rowStartIndex + 16 ; for (int j = rowStartIndex; j < rowEndIndex; j++) { dump.append(BYTE2HEX[getUnsignedByte(buf, j)]); } dump.append(" |" ); for (int j = rowStartIndex; j < rowEndIndex; j++) { dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]); } dump.append('|' ); } if (remainder != 0 ) { int rowStartIndex = (fullRows << 4 ) + startIndex; appendHexDumpRowPrefix(dump, fullRows, rowStartIndex); int rowEndIndex = rowStartIndex + remainder; for (int j = rowStartIndex; j < rowEndIndex; j++) { dump.append(BYTE2HEX[getUnsignedByte(buf, j)]); } dump.append(HEXPADDING[remainder]); dump.append(" |" ); for (int j = rowStartIndex; j < rowEndIndex; j++) { dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]); } dump.append(BYTEPADDING[remainder]); dump.append('|' ); } dump.append(StringUtil.NEWLINE + "+--------+-------------------------------------------------+----------------+" ); } private static void appendHexDumpRowPrefix (StringBuilder dump, int row, int rowStartIndex) { if (row < HEXDUMP_ROWPREFIXES.length) { dump.append(HEXDUMP_ROWPREFIXES[row]); } else { dump.append(StringUtil.NEWLINE); dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L )); dump.setCharAt(dump.length() - 9 , '|' ); dump.append('|' ); } } public static short getUnsignedByte (ByteBuffer buffer, int index) { return (short ) (buffer.get(index) & 0xFF ); } }
2.3.2 分配方法 1 2 3 4 5 6 7 8 9 public static void main (String[] args) { System.out.println(ByteBuffer.allocate(16 ).getClass()); System.out.println(ByteBuffer.allocateDirect(16 ).getClass()); }
2.3.3 写入和读取 写入:
调用channel的read方法
调用buffer的put方法
读取:
调用channel的write方法
调用buffer的get方法
注意:buffer的get方法会让指针往后走,如果要重复读数据,可以调用rewind方法将指针重置到0或者使用get(int i)方法获取指针的内容,该方法不会产生指针的位移
2.3.4 mark和reset
mark 做一个标记,记录position位置,reset 是将position回到mark位置
2.3.5 字符串和ByteBuffer的转换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void main (String[] args) { ByteBuffer buffer = ByteBuffer.allocate(16 ); buffer.put("hello" .getBytes()); ByteBuffer hello = StandardCharsets.UTF_8.encode("hello" ); ByteBuffer wrap = ByteBuffer.wrap("hello" .getBytes()); buffer.flip(); System.out.println(StandardCharsets.UTF_8.decode(buffer)); System.out.println(StandardCharsets.UTF_8.decode(hello)); System.out.println(StandardCharsets.UTF_8.decode(wrap)); }