美文网首页
51、【JavaSE】【Java 新特性等】NIO

51、【JavaSE】【Java 新特性等】NIO

作者: yscyber | 来源:发表于2021-09-18 04:54 被阅读0次
NIO-1 NIO-2 NIO-3 NIO-4 NIO-5 NIO-6 NIO-7 NIO-8 NIO-9 NIO-10 NIO-11

2、上述总结

2.1、java.nio.Buffer中的关键属性

  • Buffer 刚建立:

CapacityLimit 相等,均是缓冲区的最大容量

import java.nio.ByteBuffer;

public class Main {

    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);

        System.out.println(buffer.capacity()); // capacity = 10
        System.out.println(buffer.position()); // position = 0
        System.out.println(buffer.mark()); // limit = 0
    }

}
  • 向 Buffer 中添加(不越界):

Position 发生变化

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class Main {

    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);

        System.out.println(buffer.capacity());
        System.out.println(buffer.position());
        System.out.println(buffer.limit());

        buffer.put("Java".getBytes(StandardCharsets.UTF_8)); // 4个字节

        System.out.println(buffer.capacity()); // 10
        System.out.println(buffer.position()); // 4,区间 [0~3] 已被使用
        System.out.println(buffer.limit()); // 10
    }

}
  • 调用flip方法:

Limit 的值先变为 Position 的值,Position 变为 0

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class Main {

    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);

        System.out.println("init capacity: " + buffer.capacity()); // 10
        System.out.println("init position: " + buffer.position()); // 0
        System.out.println("init limit: " + buffer.limit()); // 10

        buffer.put("Java".getBytes(StandardCharsets.UTF_8));

        System.out.println("after add “Java” capacity: " + buffer.capacity()); // 10
        System.out.println("after add “Java” position: " + buffer.position()); // 4
        System.out.println("after add “Java” limit: " + buffer.limit()); // 10
        
        buffer.flip();

        System.out.println("after flip capacity: " + buffer.capacity()); // 10
        System.out.println("after flip position: " + buffer.position()); // 0
        System.out.println("after flip limit: " + buffer.limit()); // 4
    }

}

2.2、使用java.nio.channels.FileChanneljava.nio.ByteBuffer实现文件拷贝

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class Main {

    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("E:\\source.txt");
        FileOutputStream fos = new FileOutputStream("E:\\copy.txt");

        // 读通道
        FileChannel inChannel = fis.getChannel();
        // 写通道
        FileChannel outChannel = fos.getChannel();

        // 缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 利用 NIO 拷贝文件

        // Channel 的 read 方法:将数据先放入缓冲区中,会使 Buffer 的 position 发生变化
        while (inChannel.read(buffer) != -1) {
            // 切换 读模式
            buffer.flip();
            // Channel 的 write 方法:将缓冲区中的数据放出
            outChannel.write(buffer);
            // 使用 clear 方法,因为之前的 flip 方法改变了 position 和 limit,需要将其还原会 Buffer 刚创建的状态,便于下一次放数据到缓冲区
            buffer.clear();
        }

        // 关流还是不要忘记的
        if (fos != null) {
            fos.close();
        }
        if (fis != null) {
            fis.close();
        }
    }

}

2.3、NIO 网络编程

  • 与传统的 IO 相比,NIO 在网络编程方面一个比较大的特点就是非阻塞。传统的 IO 中,服务端需要等待客户端与之建立连接,这个过程中,服务端除了等待其他的什么都做不了,就是“阻塞”;而使用 NIO 的服务端可以在等待的过程中做一些其他的事情,直到有客户端与之建立连接,即“非阻塞”。

  • 下面的代码,NIO 实现阻塞式的网络编程(传递一个字符串):

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

// 服务端
public class NIOServer {

    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8888));

        // 阻塞,等待客户端建立连接
        SocketChannel socketChannel = serverSocketChannel.accept();

        // 建立缓冲区,等待接收客户端数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 读取客户端发送的数据至 Buffer
        int len = socketChannel.read(buffer);

        // 字节数组 转换为 字符串
        String receiveStr = new String(buffer.array(), 0, len);
        System.out.println(receiveStr);

        if (socketChannel != null) {
            socketChannel.close();
        }

        if (serverSocketChannel != null) {
            serverSocketChannel.close();
        }
    }

}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

// 客户端
public class NIOClient {

    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("Hello World!".getBytes(StandardCharsets.UTF_8));

        // 数据需要向外发,相当于得从 Buffer 中读,所以需要用 flip 方法
        buffer.flip();

        // 发送数据
        socketChannel.write(buffer);

        if (socketChannel != null) {
            socketChannel.close();
        }
    }

}
  • 下面的代码,NIO 实现阻塞式的网络编程(传递一个文件):
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.UUID;

// 服务端,接收文件
public class NIOServer {

    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8888));

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        FileOutputStream fos = new FileOutputStream("E:\\" + UUID.randomUUID() + ".txt");
        FileChannel fileChannel = fos.getChannel();


        SocketChannel sc = ssc.accept();

        // 注意 read 和 write 的语义
        while (sc.read(buffer) != -1) {
            buffer.flip();
            fileChannel.write(buffer);
            buffer.clear();
        }

        if (fileChannel != null) {
            fileChannel.close();
        }

        if (fos != null) {
            fos.close();
        }

        if (sc != null) {
            sc.close();
        }

        if (ssc != null) {
            ssc.close();
        }
    }

}
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

// 客户端,发送文件
public class NIOClient {

    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        FileInputStream fis = new FileInputStream("E:\\source.txt");
        FileChannel fileChannel = fis.getChannel();

        // 注意 read 和 write 的语义
        while (fileChannel.read(buffer) != -1) {
            buffer.flip();
            sc.write(buffer);
            buffer.clear();
        }

        if (fileChannel != null) {
            fileChannel.close();
        }

        if (fis != null) {
            fis.close();
        }

        if (sc != null) {
            sc.close();
        }
    }

}
  • 下面的代码,NIO 实现非阻塞式的网络编程(传递一个字符串):
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

// 服务端,接收方
public class NIOServer {

    public static void main(String[] args) throws IOException, InterruptedException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8888));

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        SocketChannel sc = null;

        // 设置 非阻塞
        ssc.configureBlocking(false);

        // 非阻塞 情况下的代码建议使用循环,因为在非阻塞的情况下,accept 方法无法将代码“暂停”
        while (true) {
            sc = ssc.accept();

            // sc 是否为 null 成为客户端与其是否建立连接的依据
            if (sc != null) {
                int len = sc.read(buffer);
                System.out.println(new String(buffer.array(), 0, len, StandardCharsets.UTF_8));

                // 假定接收到就结束程序
                break;
            } else {
                // sc == null:没有客户端建立连接,可以进行其他操作
                System.out.println("进行其他操作");
                // 为了运行效果,防止输出过快
                Thread.sleep(1000);
            }
        }

        if (sc != null) {
            sc.close();
        }

        if (ssc != null) {
            ssc.close();
        }
    }

}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

// 客户端,发送方
public class NIOClient {

    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("北京明白!".getBytes(StandardCharsets.UTF_8));

        buffer.flip();

        sc.write(buffer);

        if (sc != null) {
            sc.close();
        }
    }

}

3、NIO 网络编程之多路复用

NIO-12 NIO-13 NIO-14 NIO-15 NIO-16 NIO-17 NIO-18

3.1、如何理解 NIO 的多路复用

  • 在传统的 IO 网络编程中,如果想实现一次监听多个端口,需要通过多线程切换来实现

  • NIO 网络编程,提供了多路复用器,也就是借助这个多路复用器,在一个线程中就能够监听多个端口。这样编程的时候,在一个线程中可以采用轮询的策略,一旦选择器查询到通道处于之前约定好的某种状态,便认为这个通道已经就绪,可以开始通信。

“通道处于之前约定好的某种状态”中的状态

3.2、NIO 网络编程之多路复用举例

  • 服务端需一次性监听8887、8888、8889三个端口。客户端向服务端发送数据(一次只发送一个字符串)。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

// 服务端,监听多个端口,接收数据
public class NIOServer {

    public static void main(String[] args) throws IOException {
        // 监听端口 8887
        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        ssc1.bind(new InetSocketAddress(8887));
        ssc1.configureBlocking(false);

        // 监听端口 8888
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        ssc2.bind(new InetSocketAddress(8888));
        ssc2.configureBlocking(false);

        // 监听端口 8889
        ServerSocketChannel ssc3 = ServerSocketChannel.open();
        ssc3.bind(new InetSocketAddress(8889));
        ssc3.configureBlocking(false);

        // 多路复用选择器
        Selector selector = Selector.open();

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 将通道注册到选择器上,便于选择器“查询”
        ssc1.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);
        ssc3.register(selector, SelectionKey.OP_ACCEPT);

        // 采用轮询的编程策略,一旦轮询到的通道达到之前约定的状态(这里是 OP_ACCEPT),便认为通道已经就绪,这里来自客户端的可以接收数据
        // 一般场景:服务器等待客户端与其建立连接,约定的状态一般设为 OP_ACCEPT 即可

        // select 方法返回值大于 0,说明存在就绪的通道
        while (selector.select() > 0) {
            // 获取保存有 已就绪通道 Key 的 Set 的迭代器
            Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();
            // 使用迭代器遍历保存有 已就绪通道 Key 的 Set
            while (selectionKeyIterator.hasNext()) {
                SelectionKey currentReadyChannelKey = selectionKeyIterator.next();

                // 开始传输数据
                // 获取当前 Key 对应的 ServerSocketChannel
                ServerSocketChannel tempSsc = (ServerSocketChannel) currentReadyChannelKey.channel();
                SocketChannel sc = tempSsc.accept();
                int len = sc.read(buffer);
                System.out.println(new String(buffer.array(), 0, len, StandardCharsets.UTF_8));

                // 一次传输结束后的必要操作,比如清空缓冲区等
                // 注意:ServerSocketChannel tempSsc 不用 close,因为获取到的 ServerSocketChannel 对象与最开始定义的 ServerSocketChannel 对象是同一个。一旦 close 便无法再监听端口
                buffer.clear();
                if (sc != null) {
                    sc.close();
                }
                selectionKeyIterator.remove();
            }
        }

        if (ssc1 != null) {
            ssc1.close();
        }
        if (ssc2 != null) {
            ssc2.close();
        }
        if (ssc3 != null) {
            ssc3.close();
        }
    }

}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

// 客户端,端口 8887
public class NIOClientOne {

    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8887));

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        buffer.put("Hello!".getBytes(StandardCharsets.UTF_8));

        buffer.flip();

        sc.write(buffer);

        if (sc != null) {
            sc.close();
        }
    }

}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

// 客户端,端口 8887
public class NIOClientThree {

    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8887));

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        buffer.put("Java!".getBytes(StandardCharsets.UTF_8));

        buffer.flip();

        sc.write(buffer);

        if (sc != null) {
            sc.close();
        }
    }

}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

// 客户端,端口 8888
public class NIOClientTwo {

    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        buffer.put("Hi!".getBytes(StandardCharsets.UTF_8));

        buffer.flip();

        sc.write(buffer);

        if (sc != null) {
            sc.close();
        }
    }

}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

// 客户端,端口 8889
public class NIOClientFour {

    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8889));

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        buffer.put("yyds!".getBytes(StandardCharsets.UTF_8));

        buffer.flip();

        sc.write(buffer);

        if (sc != null) {
            sc.close();
        }
    }

}

相关文章

网友评论

      本文标题:51、【JavaSE】【Java 新特性等】NIO

      本文链接:https://www.haomeiwen.com/subject/enysgltx.html