Java NIO

Java NIO

Java BIO

Java的BIO中的B值得是blocking,意思是阻塞。BIO是同步非阻塞的IO模型,当线程连接客户端时,若客户端没有发送新的数据,该线程就会阻塞,无法进行其他操作。因此一个服务端的线程只能处理一个客户端的连接,也就是说每出现一个客户端连接时,服务端就需要新建一个线程来处理该连接,当连接数过多时,就会造成线程过多使得资源占用过大的情况。

Java BIO
BIO测试代码:

    public static void main(String[] args) throws IOException {
        ExecutorService executorService = Executors.newCachedThreadPool();

        ServerSocket serverSocket = new ServerSocket(2333);
        System.out.println("服务器启动...");
        while (true) {
            System.out.println("等待连接...");
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端链接...");
            // 收到信息新建进程处理客户端发来的信息
            executorService.execute(() -> {
                byte[] bytes = new byte[1024];
                try (InputStream inputStream = socket.getInputStream()) {
                    while (true) {
                        int read = inputStream.read(bytes);
                        if (read != -1) {
                            System.out.println(new String(bytes, 0, read));
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("关闭链接");
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

java NIO

Java NIO 是同步非阻塞的,即线程未收到消息时并不会像BIO一样阻塞,而可以去做其他事情。NIO服务端通过Channel通道和客户端连接,一个线程通过选择器管理多个通道,进而可以做到一个线程管理多个连接,使得在连接数目很大的时候性能大大强于BIO。
Java NIO
Java NIO是面向缓冲区的,缓冲区本质上是可以读写的内存块,NIO读写文件必须经过Buffer。

Buffer有三个需要了解的属性:

名称意义
postion缓冲区读写操作的指针,操作一次position+1,相当于下一次操作位置的下标
limit对缓冲区存取数据的限制,读写操作不能超过该限制,可以修改但不能超过limit
capacityBuffer的容量大小,创建时确定,存储数据大小不能超过该值
    public static void main(String[] args) {
        // 可以存放5个int类型的变量
        IntBuffer intBuffer = IntBuffer.allocate(5);

        // 在buffer中存放数据
        IntStream.rangeClosed(0, 4).forEach(intBuffer::put);

        // 读写切换
        intBuffer.flip();
        // 读出数据
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    } 

Buffer缓冲

Buffer的flip()操作为读写转换,在执行该函数时,limit置为原来position的值,position置为0,从而达到读写切换的目的:
NIO Buffer

Channel通道

NIO的Channel类似于流,但可以同时进行读写操作,即可以从Buffer中读取数据,也可以写入数据到Buffer中:

/**
     * 写入数据到文件
     */
    public static void write() {
        String msg = "Hello world!";
        try (FileOutputStream fileOutputStream = new FileOutputStream("src\\main\\java\\cn\\bobasyu\\nio\\text.txt")) {
            FileChannel fileChannel = fileOutputStream.getChannel();

            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put(msg.getBytes());

            byteBuffer.flip();
            fileChannel.write(byteBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从文件读取数据
     */
    public static void read() {
        File file = new File("src\\main\\java\\cn\\bobasyu\\nio\\text.txt");
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            FileChannel fileChannel = fileInputStream.getChannel();
            ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());

            fileChannel.read(byteBuffer);
            String msg = new String(byteBuffer.array());
            System.out.println(msg);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

同时,Channel通道还支持复制操作:

    /**
     * 复制文件
     */
    public static void copy() {
        try (FileInputStream fileInputStream = new FileInputStream("src\\main\\java\\cn\\bobasyu\\nio\\text.txt");
             FileOutputStream fileOutputStream = new FileOutputStream("src\\main\\java\\cn\\bobasyu\\nio\\text2.txt")) {
            FileChannel fileChannel = fileInputStream.getChannel();
            FileChannel fileChannel2 = fileOutputStream.getChannel();
            fileChannel2.transferFrom(fileChannel, 0, fileChannel.size());
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Selector选择器

Java NIO一个线程通过Selector选择器管理多个Channel从而实现一个线程与多个客户端连接。
Selector可以监听四种事件:

ConnectSelectionKey.OP_CONNECTselectionKey.isConnectable()
AcceptSelectionKey.OP_ACCEPTselectionKey.isAcceptable()
ReadSelectionKey.OP_READselectionKey.isReadable()
WriteSelectionKey.OP_WRITEselectionKey.isWritable()

Selector部分常用API:

名称内容
open()得到一个Selector对象
selectedKeys()得到所有的SelectionKey, 每一个Channe对应一个SelectionKey
wakeUp()调用后使得阻塞的select()方法立即返回
select()阻塞到有注册的事件就绪为止
select(long timeout)在超时时间内阻塞到有注册的事件就绪为止

服务端样例代码:

 public static void main(String[] args) throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        Selector selector = Selector.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(8888));
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 把serverSocketChannel注册到Selector,关心事件为OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 若没有事件发生
            if (selector.select(1000) == 0) {
                System.out.println("服务器一秒钟内无事发生...");
                continue;
            }
            // 若已经获取到关注的事件
            // 通过selectedKeys()反向获取通道
            Set<SelectionKey> selectionKeySet = selector.selectedKeys();
            Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
            if (selectionKeyIterator.hasNext()) {
                SelectionKey selectionKey = selectionKeyIterator.next();
                // 如果是OP_ACCEPT事件, 即客户端已连接
                if (selectionKey.isAcceptable()) {
                    System.out.println("客户端连接成功...");
                    // 获取该客户端的socketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    // 将socketChannel 注册到Selector上, 关注事件为OP_READ, 并关联一个Buffer
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                // 如果是OP_READ事件,即收到数据
                if (selectionKey.isReadable()) {
                    // 通过key获取channel
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    // 获取channel所关联的Buffer
                    ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                    socketChannel.read(byteBuffer);
                    System.out.println("客户端:" + new String(byteBuffer.array()));
                }
                // 手动一处SelectionKey, 防止重复操作
                selectionKeyIterator.remove();
            }
        }
    }

客户端:

public class NIOClient {
    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        // 设置非阻塞
        socketChannel.configureBlocking(false);
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);
        if (!socketChannel.connect(inetSocketAddress)) {
            while (!socketChannel.finishConnect()) {
                System.out.println("连接中,可以进行其他操作...");
            }
            System.out.println("连接成功...");

            Scanner scanner = new Scanner(System.in);
            String msg = scanner.next();

            // 根据字符串大小自动生成buffer大小
            ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
            // 发送数据,将buffer数据写入channel
            socketChannel.write(byteBuffer);

        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值