Java-NIO

Java NIO

  • NIO概述
    • NIO与传统 IO的区别
    • 通道(Channel)
      • 直接与非直接缓冲区
    • 通道& 缓冲区(Buffer)
  • NIO非阻塞式网络通信
    • 阻塞式例子
    • 非阻塞式
  • 管道(Pipe)

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): 字节数组 -> 字符串

Java-NIO_第1张图片

  • 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自动回收的问题. 因此建议只用于, 保存长时间持久的基础信息

Java-NIO_第2张图片

Java-NIO_第3张图片

通道& 缓冲区(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

Java-NIO_第4张图片

  • 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()通道写入并传输数据

Java-NIO_第5张图片


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,基础知识,New,IO,Non-Blocking,IO,NIO,Buffer,Channel)