切换菜单
搜索
个人笔记云
首页
java
spring
springmvc
python
使用教程
笔记管理
搜索
登录/注册
好物分享
退出
搜索
Java-NIO详解
2021-02-22
440
## Java-NIO详解 NIO概述 ===== * Java NIO(New IO, Non-Blocking IO)是从 Java 1.4开始引入的全新的 IO. 特点是同步非阻塞, 面向缓冲区的 NIO与传统 IO的区别 ------------ | IO | NIO | |-------------------------|:-------------------------:| | 面向流(Stream Oriented)单向的 | 面向缓冲区(Buffer Oriented)双向的 | | 阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) | | - | 选择器(Selectors) | `*传统 IO操作是 DMA(Direct Memory Access, 直接存储器访问;`特点是操作时需往 CPU请求获取权限`)负责 IO接口与内存的交互. 而 NIO是通过 Channel与内存交互, 相比 DMA方式 Channel方式是独立的, 因此性能略高` 通道(Channel) ----------- * NIO的 Channel类似于传统的"流", 但它本身是不能直接访问数据, 而只负责传输, 数据的存取是使用了缓冲区(Buffer) > * 通道的主要实现类 > (-) FileChannel: 读写文件的通道 > (-) SocketChannel: 通过 TCP连接, 读写网络数据的通道 > (-) ServerSocketChannel: 监听 TCP连接, 为每一个连接都会创建一个 SocketChannel > (-) DatagramChannel: 通过 UDP连接, 读写网络数据的通道 > * 获取通道 > > 1. 支持通道的类, 可通过 getChannel()方法获取通道 > (1-1) 本地 IO: > (-) FileInputStream/FileOutputStream > (-) RandomAccessFile > (1-2) 网络 IO: > (-) Socket > (-) ServerSocket > (-) DatagramSocket > `* 其它: 可以通过 Files类的静态方法 newByteChannel()获取字节通道. 或可以通过通道的静态方法 open()打开并返回指定通道` > > * 通道之间的数据传输 > (-) inFileChannel.transferTo(long position, long count, WritableByteChannel target): 从源(inFileChannel)传输到 target > (-) outFileChannel.transferFrom(ReadableByteChannel src, long position, long count): 从(src)源传输到 outFileChannel > * 分散(Scatter)\& 聚集(Gather) > (-) 分散读取(Scattering Reads): 将通道中的数据分散到多个缓冲区中. 注: 按照缓冲区的顺序依次填满 Buffer > (-) 聚集写入(Gathering Writes): 将多个 Buffer中的数据"聚集"到一个 Channel. 注: 按照缓冲区的顺序写入, 从 position到 limit之间的数据到 Channel > * 字符集(Charset) > 编码(CharsetEncoder): 字符串 -\> 字节数组 > 解码(CharsetDecoder): 字节数组 -\> 字符串 ![在这里插入图片描述](images/2403/1613964112037.png) * FileChannel的常用方法: > int read(ByteBuffer dst)从 Channel中读取数据到 dst > long read(ByteBuffer\[\] dsts)将 Channel中的数据"分散"存到 dsts > int write(ByteBuffer src)将 ByteBuffer中的数据写入到 Channel > long write(ByteBuffer\[\] srcs)将 ByteBuffer\[\]中的数据"聚集"写入到 Channel > long position()返回此通道的文件位置 > FileChannel position(long p)设置此通道的文件位置 > long size()返回此通道的文件的当前大小 > FileChannel truncate(long s)将通道中的文件截断为 s个字节 > void force(boolean metaData)将通道中还未写入到磁盘的数据, 强制写完 * 本地 Channel使用例子 ``` # 利用通道完成文件的复制(非直接缓冲区) public class TestChannel { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("E:/1.ico"); FileOutputStream fos = new FileOutputStream("E:/2.ico"); FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel(); // 分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 从 inChannel中读取数据到 buf中 while(inChannel.read(buf) != -1) { // 切换读取模式 buf.flip(); // 将 buf中的数据写入到 outChannel中 outChannel.write(buf); // 清空缓冲区 buf.clear(); } outChannel.close(); inChannel.close(); fos.close(); fis.close(); } } # 通过内存映射文件复制(直接缓冲区) public class TestChannel { public static void main(String[] args) throws IOException { FileChannel inChannel = FileChannel.open(Paths.get("E:/1.ico"), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get("E:/2.ico"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); // 内存映射文件 MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size()); MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size()); // 直接对缓冲区进行数据的读写操作 byte[] dst = new byte[inMappedBuf.limit()]; // 从 inMappedBuf中读取字节数据到 dst中 inMappedBuf.get(dst); // 将 dst中的字节数据写入到 outMappedBuf中 outMappedBuf.put(dst); inChannel.close(); outChannel.close(); } } # 通道之间的数据传输(直接缓冲区) public class TestChannel { public static void main(String[] args) throws IOException { FileChannel inChannel = FileChannel.open(Paths.get("E:/1.ico"), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get("E:/2.ico"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); // 从源(inFileChannel)传输到 target inChannel.transferTo(0, inChannel.size(), outChannel); // 从(src)源传输到 outFileChannel outChannel.transferFrom(inChannel, 0, inChannel.size()); inChannel.close(); outChannel.close(); } } # 分散和聚集 public class TestChannel { public static void main(String[] args) throws IOException { RandomAccessFile raf1 = new RandomAccessFile("E:/1.sql", "rw"); // 获取通道 FileChannel channel1 = raf1.getChannel(); // 分配指定大小的缓冲区 ByteBuffer buf1 = ByteBuffer.allocate(100); ByteBuffer buf2 = ByteBuffer.allocate(1024); // 分散读取 ByteBuffer[] bufs = {buf1, buf2}; // 将 channel1中的数据分散读取到 bufs中 channel1.read(bufs); for (ByteBuffer byteBuffer: bufs) { // 切换读取模式 byteBuffer.flip(); } System.out.println("buf1: " + new String(bufs[0].array(), 0, bufs[0].limit())); System.out.println("buf2: " + new String(bufs[1].array(), 0, bufs[1].limit())); // 聚集写入 RandomAccessFile raf2 = new RandomAccessFile("E:/2.sql", "rw"); FileChannel channel2 = raf2.getChannel(); // 将 bufs中的数据聚集写入到 channel2中 channel2.write(bufs); channel1.close(); channel2.close(); raf1.close(); raf2.close(); } } # 字符集 public class TestChannel { public static void main(String[] args) throws IOException { // 指定编码 Charset cs1 = Charset.forName("GBK"); // 获取编码器 CharsetEncoder encoder = cs1.newEncoder(); // 获取解码器 CharsetDecoder decoder = cs1.newDecoder(); // 分配指定大小的非直接缓冲区 CharBuffer buf1 = CharBuffer.allocate(1024); buf1.put("全abc12"); // 切换读取数据模式 buf1.flip(); // 编码 ByteBuffer buf2 = encoder.encode(buf1); for (int i = 0; i < 6; i++) { System.out.println(buf2.get()); } // --> -56 // --> -85 // --> 97 // --> 98 // --> 99 // --> 49 // 切换读取数据模式, 并重新配置 position buf2.flip(); // 解码 CharBuffer buf3 = decoder.decode(buf2); System.out.println(buf3.toString()); // --> 全abc1 System.out.println("-----"); Charset cs2 = Charset.forName("GBK"); // 切换读取数据模式, 并重新配置 position buf2.flip(); CharBuffer buf4 = cs2.decode(buf2); System.out.println(buf4.toString()); } } ``` ### 直接与非直接缓冲区 > 执行过程: > (-) 非直接: 当程序从磁盘读取数据时, 会首先将数据复制到物理内存中, 再将数据复制到 JVM的内存中后便可取到数据 > (-) 直接: 在物理内存中建立一个缓冲区, 省略了复制到 JVM的步骤. 因此性能优于非直接缓冲区 > 分配缓冲区: > (-) 非直接: 通常是通过 allocate()方法来分配缓冲区, 将缓冲区建立在 JVM的内存中 > (-) 直接: 可以通过 allocateDirect()指定缓冲区, FileChannel.map()内存映射文件或 FileChannel.transferTo()和 FileChannel.transferFrom()通道之间的数据传输等, 将缓冲区建立在物理内存中 > 通过 isDirect()方法来判断是否为直接缓冲区 > `* 虽然直接缓冲区性能好, 不过使用时, 有一定安全隐患及不被 GC自动回收的问题. 因此建议只用于, 保存长时间持久的基础信息` ![在这里插入图片描述](images/2403/1613964112071.png) ![在这里插入图片描述](images/2403/1613964112127.png) 通道\& 缓冲区(Buffer) ---------------- * NIO的 Buffer是用于与通道进行交互的, 从通道将数据读入到缓冲区, 从缓冲区写入通道的 > * 常用子类 > (-) ByteBuffer, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, DoubleBuffer > * 基本属性 > (-) capacity: Buffer的最大容量. capacity不能为负, 且创建后不能更改 > (-) limit: 第一个不应该读取或写入的数据索引. 即位于 limit后的数据不可读写. 且不能大于 capacity > (-) position: 下一个要读取或写入的数据索引. position不能为负, 且不能大于 limit > (-) mark\& reset: mark是一个索引, 通过 Buffer的 mark()方法指定 Buffer中特定的 position, 之后可以通过调用 reset()方法, 将 position转到以前设置的 mark所在的位置 > (-) `* mark, position, limit, capacity遵守以下不变式: 0 <= mark <= position <= limit <= capacity` ![在这里插入图片描述](images/2403/1613964112165.png) * Buffer的常用方法: > Buffer allocate(int capacity)分配非直接缓冲区 > Buffer allocateDirect(int capacity)分配直接缓冲区 > Buffer clear()清空缓冲区, 并返回对缓冲区的引用. 但清空后数据依然存在, 只不过数据状态是被遗忘的状态 > Buffer flip()将缓冲区的界限设置为当前位置, 并将当前 position设置为0;切换为读模式 > int capacity()返回 Buffer的 capacity大小 > boolean hasRemaining()判断缓冲区中是否还有元素 > int limit()返回 Buffer的 limit(界限)的位置 > Buffer limit(int n)将设置缓冲区界限为 n, 并返回一个具有新 limit的缓冲区对象 > int position()返回缓冲区的当前 position > Buffer position(int n)设置缓冲区的当前位置为 n, 并返回修改后的 Buffer对象 > int remaining()返回 position和 limit之间的元素个数 > Buffer mark()为指定 position设置标记 > Buffer reset()将 position设置为 mark所在的位置;丢弃 mark位置之后的数据, 重新从 mark位置开始写入(`注: mark必须为已设置`) > Buffer rewind()将 position设为 0, 取消 mark设置; 可以重复读数据 > > * 缓冲区的数据操作: > * 获取 Buffer中的数据: > get(): 读取单个字节 > get(byte\[\] dst): 批量读取多个字节到 dst中 > get(int index): 读取指定索引位置的字节(不会移动 position) > * 放入数据到 Buffer中: > put(byte b): 将给定单个字节写入缓冲区的当前位置 > put(byte\[\] src): 将 src中的字节写入缓冲区的当前位置 > put(int index, byte b): 将指定字节写入缓冲区的索引位置(不会移动 position) * Buffer使用例子 ``` public class TestBuffer { public static void main(String[] args) { String str = "abcde"; // 1. 分配指定大小的非直接缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println(buf.capacity()); // --> 1024 System.out.println("----- allocate() -----"); System.out.println(buf.position()); // --> 0 System.out.println(buf.limit()); // --> 1024 // 2. 存入数据到缓冲区中 buf.put(str.getBytes()); System.out.println("----- put() -----"); System.out.println(buf.position()); // --> 5 System.out.println(buf.limit()); // --> 1024 // 3. 切换读取数据模式, 并重新配置 position buf.flip(); System.out.println("----- flip() -----"); System.out.println(buf.position()); // --> 0 System.out.println(buf.limit()); // --> 5 // 4. 读取缓冲区中的数据 byte[] dst = new byte[buf.limit()]; // 4.1 将 buf中的数据输入到 dst, 并重新配置 position buf.get(dst); System.out.println(new String(dst, 0, dst.length)); // --> abcde System.out.println("----- get() -----"); System.out.println(buf.position()); // --> 5 System.out.println(buf.limit()); // --> 5 // 5. 可重复读, 将 position设为 0, 并取消 mark设置 buf.rewind(); System.out.println("----- rewind() -----"); System.out.println(buf.position()); // --> 0 System.out.println(buf.limit()); // --> 5 // 6. 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态 buf.clear(); System.out.println("----- clear() -----"); System.out.println(buf.position()); // --> 0 System.out.println(buf.limit()); // --> 1024 // 6.1 数据状态为被遗忘, 但依然可以输出 System.out.println((char) buf.get()); // --> a // 是否为直接缓冲区 System.out.println("----- isDirect() -----"); System.out.println(buf.isDirect()); // --> false } } public class TestBuffer { public static void main(String[] args) { String str = "abcde"; // 分配指定大小的非直接缓冲区 ByteBuffer buf = ByteBuffer.allocate(5); // 存入数据到缓冲区中 buf.put(str.getBytes()); // 切换读取数据模式, 并重新配置 position buf.flip(); byte[] dst2 = new byte[buf.limit()]; buf.get(dst2, 0, 2); System.out.println("----- flip() -----"); System.out.println(new String(dst2, 0, 2)); // --> ab System.out.println(buf.position()); // --> 2 // 标记 buf.mark(); // 标记 position = 2 buf.get(dst2, 2, 3); System.out.println(new String(dst2, 2, 2)); // --> cd System.out.println(buf.position()); // --> 5 System.out.println("----- hasRemaining() -----"); System.out.println(buf.hasRemaining()); // --> false // 恢复到 mark的位置 buf.reset(); System.out.println("----- reset() -----"); System.out.println(buf.position()); // --> 2 // 判断缓冲区中是否还有剩余数据 if(buf.hasRemaining()){ // 获取缓冲区中可以操作的数量 System.out.println("----- remaining() -----"); System.out.println(buf.remaining()); // --> 3 } } } ``` NIO非阻塞式网络通信 =========== * 阻塞与非阻塞 > * 阻塞式(IO): 通过传统 IO处理流时, 指定线程会被阻塞. 也就是说, 当使用传统的 IO构建网络通信时, 服务器端会为每个客户端, 开辟独立的线程来维持连接, 由此引起, 性能急剧下降 > * 非阻塞式(NIO): 在通道连接的状态下, 若没有数据可用时, 该线程可以进行其它任务. 如处理其它通道上的 IO, 也就是说, 一个线程可以管理多个输入输出通道. 因此, 区别于阻塞式, 通过非阻塞式实现的网络通信服务器, 可以将有限的线程(性能)更有效的利用来降低性能上的成本, 由此, 同步更多的客户端 阻塞式例子 ----- * 通过 Channel\& Buffer, 传输文件 ``` public class NIOBlockingSocketServer { public static void main(String[] args) throws IOException { // 1. 获取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); // 2. 绑定连接 ssChannel.bind(new InetSocketAddress(9898)); // 3. 获取本地文件通道, 写入模式(文件不存在, 则创建) FileChannel outChannel = FileChannel.open(Paths.get("C:\\Users\\Shawn Jeon\\Pictures\\2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE); // 4. 获取客户端连接的通道 SocketChannel sChannel = ssChannel.accept(); // 5. 分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 6. 从 sChannel(客户端通道)接收数据, 并保存到 buf while (sChannel.read(buf) != -1) { // 切换读取模式 buf.flip(); // 往 outChannel通道(本地文件), 写入数据 outChannel.write(buf); // 清空缓冲区 buf.clear(); } // 反馈信息到客户端 buf.put("服务端接收数据成功".getBytes()); // 切换读取模式 buf.flip(); // 往 sChannel通道(客户端), 发送数据 sChannel.write(buf); // 7. 关闭通道 sChannel.close(); outChannel.close(); ssChannel.close(); } } public class NIOBlockingSocketClient { public static void main(String[] args) throws IOException { // 1. 获取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898)); // 2. 获取本地文件通道, 读取模式 FileChannel inChannel = FileChannel.open(Paths.get("C:\\Users\\Shawn Jeon\\Pictures\\1.jpg"), StandardOpenOption.READ); // 3. 分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 4. 从 inChannel读取数据, 并保存到 buf while (inChannel.read(buf) != -1) { // 切换读取模式 buf.flip(); // 往 sChannel通道(服务器端), 写入数据 sChannel.write(buf); // 清空缓冲区 buf.clear(); } // 数据发送完毕, 不再发送更多数据 sChannel.shutdownOutput(); // 接收服务器端的反馈 int len = 0; // 从 sChannel(服务器端通道)接收数据, 并保存到 buf while ((len = sChannel.read(buf)) != -1) { // 切换读取模式 buf.flip(); // 打印反馈内容 System.out.println(new String(buf.array(), 0, len)); // 清空缓冲区 buf.clear(); } // 5. 关闭通道 inChannel.close(); sChannel.close(); } } ``` 非阻塞式 ---- * Selector(选择器) > * Selector是 Channel的多路复用器, NIO将所有的 Channel都注册到 Selector上, 来监控各个 Channel的 IO状态, 使一个线程管理多个 Channel > > > > * 创建\& 注册通道 > > 1. 创建选择器 Selector ServerSocketChannel::open() > 2. 向选择器注册通道 SelectionKey SelectableChannel.register(Selector sel, int ops); 注册通道时, 可以通过参数 ops指定事件 > (-) 读 SelectionKey.OP_READ = 1 \<\< 0 = 1 > (-) 写 SelectionKey.OP_WRITE = 1 \<\< 2 = 4 > (-) 连接 SelectionKey.OP_CONNECT = 1 \<\< 3 = 8 > (-) 接收 SelectionKey.OP_ACCEPT = 1 \<\< 4 = 16 > `* 若监听多个事件, 则可以使用"位或"操作符 例 int interestSet = SelectionKey.OP_READ| SelectionKey.OP_WRITE;` > > * SelectionKey常用方法描述: > int interestOps() 获取选择器中, 指定选择器键的事件集 > int readyOps() 获取已就绪的操作集 > SelectableChannel channel() 获取已注册的通道 > Selector selector() 返回创建此键的选择器 > boolean isReadable() 判断指定选择器键的 Channal的读事件是否就绪 > boolean isWritable() 判断指定选择器键的 Channal的写事件是否就绪 > boolean isConnectable() 判断指定选择器键的 Channal的连接事件是否就绪 > boolean isAcceptable() 判断指定选择器键的 Channal的接收事件是否就绪 > * Selector方法描述: > Selector open() 打开一个选择器 > boolean isOpen() 判断当前选择器是否已开启 > Set keys() 返回当前选择器关联的已注册的 SelectionKey集合 > Set selectedKeys() 返回当前选择器中已被选定(就绪)的 SelectionKey集合, 每个键都关联一个已就绪的(至少含一种操作的通道) > int select() 返回当前选择器中的就绪通道数量, 如选择器中没有"就绪通道", 则阻塞线程, 直到有就绪的通道 > int select(long timeout) 可以设置阻塞的超时时间(毫秒) > int selectNow() 返回当前选择器中的就绪通道数量, 如选择器中没有"就绪通道", 将立即返回0, 而不阻塞线程 > Selector wakeup() 使阻塞中的 select()方法立即返回 > void close() 关闭当前选择器 * TCP非阻塞式例子 ``` public class NIONonBlockingSocketServer { public static void main(String[] args) throws IOException { // 1. 获取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); // 2. 切换非阻塞模式 ssChannel.configureBlocking(false); // 3. 绑定连接 ssChannel.bind(new InetSocketAddress(9898)); // 4. 获取选择器 Selector selector = Selector.open(); // 5. 将通道注册到选择器上, 并且指定“监听接收事件” ssChannel.register(selector, SelectionKey.OP_ACCEPT); // 6. 轮询式的获取选择器上已经“准备就绪”的事件 while (selector.select() > 0) { // 7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)” Iterator
it = selector.selectedKeys().iterator(); while (it.hasNext()) { // 8. 获取准备“就绪”的是事件 SelectionKey sk = it.next(); // 9. 判断具体是什么事件准备就绪 if (sk.isAcceptable()) { // 10. 若“接收就绪”, 获取客户端连接 SocketChannel sChannel = ssChannel.accept(); // 11. 切换非阻塞模式 sChannel.configureBlocking(false); // 12. 将该通道注册到选择器上 sChannel.register(selector, SelectionKey.OP_READ); } else if (sk.isReadable()) { // 判断选定的通道是否准备“就绪”, 用于读取“接收到的数据“ // 13. 获取当前选择器上“读就绪”状态的通道 SocketChannel sChannel = (SocketChannel) sk.channel(); // 14. 分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 15. 从 sChannel(客户端通道)接收数据, 并保存到 buf int len = 0; while ((len = sChannel.read(buf)) > 0 ) { // 切换读取模式 buf.flip(); // 打印内容 System.out.println(new String(buf.array(), 0, len)); // 清空缓冲区 buf.clear(); } } // 16. 取消选择键 SelectionKey it.remove(); } } } } public class NIONonBlockingSocketClient { public static void main(String[] args) throws IOException { // 1. 获取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898)); // 2. 切换非阻塞模式 sChannel.configureBlocking(false); // 3. 分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 4. 写入数据到服务端 Scanner scan = new Scanner(System.in); while (scan.hasNext()) { String str = scan.next(); buf.put((str + " " + new Date().toString()).getBytes()); // 切换读取模式 buf.flip(); // 往 sChannel通道(服务器端), 写入数据 sChannel.write(buf); // 清空缓冲区 buf.clear(); } // 5. 关闭通道 sChannel.close(); } } ``` * UDP非阻塞式例子 ``` public class NonBlockingNIODatagramReceive { public static void main(String[] args) throws IOException { // 1. 获取通道 DatagramChannel dc = DatagramChannel.open(); // 2. 切换非阻塞模式 dc.configureBlocking(false); // 3. 给当前通道绑定端口 dc.bind(new InetSocketAddress(9898)); // 4. 获取选择器 Selector selector = Selector.open(); // 5. 将该通道注册到选择器上 dc.register(selector, SelectionKey.OP_READ); // 6. 轮询式的获取选择器上已经“准备就绪”的事件 while (selector.select() > 0) { // 7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)” Iterator
it = selector.selectedKeys().iterator(); while (it.hasNext()) { // 8. 获取准备“就绪”的是事件 SelectionKey sk = it.next(); // 判断选定的通道是否准备“就绪”, 用于读取“接收到的数据“ if (sk.isReadable()) { ByteBuffer buf = ByteBuffer.allocate(1024); // 从 DatagramChannel接收数据, 并保存到 buf dc.receive(buf); // 切换读取模式 buf.flip(); // 打印内容 System.out.println(new String(buf.array(), 0, buf.limit())); // 清空缓冲区 buf.clear(); } } // 9. 取消选择键 SelectionKey it.remove(); } } } public class NonBlockingNIODatagramSend { public static void main(String[] args) throws IOException { // 1. 获取通道 DatagramChannel dc = DatagramChannel.open(); // 2. 切换非阻塞模式 dc.configureBlocking(false); // 3. 分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 4. 写入数据到服务端 Scanner scan = new Scanner(System.in); while (scan.hasNext()) { String str = scan.next(); buf.put((str + " " + new Date().toString()).getBytes()); // 切换读取模式 buf.flip(); // 往指定 UDP接收端的地址, 发送数据 dc.send(buf, new InetSocketAddress("127.0.0.1", 9898)); // 清空缓冲区 buf.clear(); } // 5. 关闭通道 dc.close(); } } ``` 管道(Pipe) ======== * Java NIO在 JVM内不同线程之间通过管道单向传输数据. 通过 pipe.source()通道读取数据, 再通过 pipe.sink()通道写入并传输数据 ![在这里插入图片描述](images/2403/1613964112208.png) ``` public class PipeTest { public static void main(String[] args) throws IOException { // 获取管道 Pipe pipe = Pipe.open(); // 分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 获取 sink管道,用来传送数据 Pipe.SinkChannel sinkChannel = pipe.sink(); buf.put("通过管道单向发送数据".getBytes()); buf.flip(); // 传送数据 sinkChannel.write(buf); // 获取 source管道, 用来接收管道数据 Pipe.SourceChannel sourceChannel = pipe.source(); buf.flip(); // 读取数据 int len = sourceChannel.read(buf); // 打印内容 System.out.println(new String(buf.array(), 0, len)); // 关闭通道 sourceChannel.close(); sinkChannel.close(); } } ``` ## 好书分享 **Java并发编程实战.pdf**
**Java性能权威指南.pdf**
**实战Java虚拟机.pdf**
**深入JAVA虚拟机第二版.pdf**
**Java程序员修炼之道**
**http://www.notescloud.top/cloudSearch/detail?id=1661**
**疯狂Java-突破程序员基本功的16课**
**Java8实战.pdf**
**轻量级JAVAEE企业应用实战(第3版_含源码)**
**阿里巴巴Java开发手册.pdf**
**《疯狂Java讲义第4版》PDF及代码**
**Java研发军团整理《Java面试题手册》V1.0版**
**ThinkingInJava(java思想第四版)中文版.pdf**
[原文链接](https://blog.csdn.net/qcl108/article/details/113921886)
教程分类
热门视频教程
热门文章
热门书籍推荐