java基础--NIO

本文深入讲解Java NIO的核心组件:Buffer缓冲区、Channel通道及Selector选择器。涵盖Buffer的创建与常用方法、Channel的基本使用及Selector的应用场景,适用于希望掌握NIO编程技巧的开发者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1Buffer缓冲区

1.1概述

1.2ByteBuffer的创建方式

1.3常用方法

1.3.1给数组添加元素

1.3.2获取容量

1.3.3限制

1.3.4位置

1.3.5标记

1.3.6还原缓冲区的状态

1.3.7切换读写状态

2Channel通道

2.1概述

2.2分类

2.3FileChannel基本使用

2.4 FileChannel结合MappedByteBuffer实现读写

2.5网络编程收发信息

2.6accept阻塞

3Selector选择器

3.1多路复用概念

3.2 Selector介绍

3.3Selector选择器的使用

3.4Selector管理多个服务端


        Non-blocking IO,NIO包含三个部分,分别是Buffer缓冲区、Channel通道、Selector选择器,NIO又叫做非阻塞IO,可以提高高并发情况下程序的效率。

1Buffer缓冲区

1.1概述

        Buffer叫缓冲区数组,能够替换之前的普通数组byte[]。用来在后面的NIO的读写中提高效率。

        分类:

ByteBuffer        byte[]   
CharBuffer        char[]
DoubleBuffer         double[]
FloatBuffer        float[]
IntBuffer         int[]
LongBuffer         long[]
ShortBuffer        short[]

1.2ByteBuffer的创建方式

        代码演示:

        //在堆中创建缓冲区:allocate(int capacity)

        ByteBuffer buffer1 = ByteBuffer.allocate(10);

        //在系统内存创建缓冲区:allocatDirect(int capacity)

        ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);

        //通过数组创建缓冲区:wrap(byte[] arr)
        byte[] arr = new byte[1024];
        ByteBuffer buffer3 = ByteBuffer.wrap(arr);

1.3常用方法

方法说明
put(byte b)给数组添加元素
capacity()获取容量
limit()限制
position()位置
mark()标记
clear()还原缓冲区的状态
flip()切换读写状态

1.3.1给数组添加元素

        //创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(10);

        //给数组添加元素
        buffer.put((byte) 11);
        buffer.put((byte) 22);
        buffer.put((byte) 33);
        buffer.put((byte) 44);
        byte[] arr = {55,66,77};
        buffer.put(arr);
        buffer.put(arr,0,2);
        buffer.put(9, (byte) 99);

        //把byteBuffer转成普通数组
        byte[] array = buffer.array();
        System.out.println(Arrays.toString(array));//[11, 22, 33, 44, 55, 66, 77, 55, 66, 99]

1.3.2获取容量

        容量是一个定值。

        //获取容量
        int capacity = buffer.capacity();
        System.out.println(capacity);//10

1.3.3限制

        limit可以指定一个索引,从limit开始后面的位置不能操作。

        //获取限制(不带参是获取)
        int limit = buffer.limit();
        System.out.println(limit);     //10

        //赋值限制(带参数是赋值)
        buffer.limit(6);            //从5索引开始就不能操作了

1.3.4位置

        位置代表将要存放的元素的索引,每次添加position往后移动。位置不能小于0,也不能大于limit。

        //创建对象
        ByteBuffer buffer = ByteBuffer.allocate(10);

        //添加
        buffer.put((byte)11);
        buffer.put((byte)22);

        //position范围 [0,limit]  不带参数是获取范围
        int position = buffer.position();
        System.out.println(position);//2
        //带参数是指定范围
        buffer.position(5);
        buffer.put((byte)33);
        System.out.println(Arrays.toString(buffer.array()));//[11, 22, 0, 0, 0, 33, 0, 0, 0, 0]
        
        System.out.println(buffer);  //[pos=2 lim=10 cap=10]

1.3.5标记

        当调用缓冲区的reset()重置方法时,会将缓冲区的position位置重置为mark的位置。

        //创建对象
        ByteBuffer buffer = ByteBuffer.allocate(10);

        //添加
        buffer.put((byte)11);
        buffer.put((byte)22);

        //position范围 [0,limit]
        System.out.println(buffer);  //[pos=2 lim=10 cap=10]

        //设置mark标记
        buffer.mark();   //把此时索引标记2

        //添加
        buffer.put((byte)33);
        buffer.put((byte)44);

        //重置(此时position会被重置到mark的位置)
        buffer.reset();

        buffer.put((byte)55);

        System.out.println(Arrays.toString(buffer.array()));//[11, 22, 55, 44, 0, 0, 0, 0, 0, 0]

1.3.6还原缓冲区的状态

        将position设置为:0

        将限制limit设置为容量capacity

        丢弃标记mark

        //创建对象
        ByteBuffer buffer = ByteBuffer.allocate(10);

        //添加
        buffer.put((byte)11);
        buffer.put((byte)22);
        //mark标记
        buffer.mark();  //2
        //limit限制
        buffer.limit(3);

        System.out.println(buffer);   //[pos=2 lim=3 cap=10]

        //clear 还原
        buffer.clear();

        System.out.println(buffer);  //[pos=0 lim=10 cap=10]

        //数组的内容
        System.out.println(Arrays.toString(buffer.array()));  //[11, 22, 0, 0, 0, 0, 0, 0, 0, 0]

1.3.7切换读写状态

        在读写数据之间要调用这个方法。

        将limit设置为当前position位置

        将当前position位置设置为0

        丢弃标记

        //创建对象
        ByteBuffer buffer = ByteBuffer.allocate(10);

        //添加
        buffer.put((byte)11);
        buffer.put((byte)22);

        System.out.println(buffer);//[pos=2 lim=10 cap=10]

        //flip切换
        buffer.flip();

        System.out.println(buffer);//[pos=0 lim=2 cap=10]

        //数组的内容
        System.out.println(Arrays.toString(buffer.array()));
        //[11, 22, 0, 0, 0, 0, 0, 0, 0, 0]

2Channel通道

2.1概述

        Channel叫通道,通道相当于之前的流对象。流是用来传输数据的,通道也是用来传输数据的,并且通道既可以读也可以写。流可以使用普通数组作为数据的载体,通道需要使用缓冲区数组作为数据的载体。

2.2分类

  • FileChannel:从文件读取数据
  • DatagramChannel:读写UDP网络协议数据
  • SocketChannel:读写TCP网络协议数据
  • ServerSocketChannel:可以监听TCP连接

2.3FileChannel基本使用

        使用FileChannel完成文件的复制

        //输入流
        FileInputStream fis = new FileInputStream(new File("src\\study\\NIO\\Test07.png"));
        //输出流
        FileOutputStream fos = new FileOutputStream(new File("src\\study\\NIO\\Test07输出.png"));
        //获取通道
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();
        //创建缓冲区数组
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //循环 读文件
        while (inChannel.read(buffer) != -1){
            //读了1024字节的文件,开始写,写之前需要进行切换
            buffer.flip();
            //输出
            outChannel.write(buffer);
            //重置,下一轮读取
            buffer.clear();
        }
        //关闭资源
        fis.close();
        fos.close();
        inChannel.close();
        outChannel.close();

2.4 FileChannel结合MappedByteBuffer实现读写

        MappedByteBuffer是ByteBuffer的子类。他可以完成数据读写。能够把硬盘中的数据一次映射到内存中。可以减少硬盘和内存之间的IO次数,能够提高效率。

        使用MappedByteBuffer完成文件复制

        //输入流
        RandomAccessFile in = new RandomAccessFile("src\\study\\NIO\\Test08.png","r");
        //输出流
        RandomAccessFile out = new RandomAccessFile("src\\study\\NIO\\Test08输出.png","rw");
        //获取通道
        FileChannel inChannel = in.getChannel();
        FileChannel outChannel = out.getChannel();
        //获取文件字节大小
        long size = inChannel.size();
        //创建缓冲数组
        MappedByteBuffer buffer1 = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
        MappedByteBuffer buffer2 = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);

        //将buffer1的字节移入到buffer2中
        for (long i = 0; i < size; i++) {
            byte b = buffer1.get();
            buffer2.put(b);
        }
        //关流
        in.close();
        out.close();
        inChannel.close();
        outChannel.close();

        一次性将文件读取到内存中,从内存中将输入缓冲区的数据写到输出缓冲区中,这个时候对于内存的压力很大,如果文件很大,内存可能不够用,所以在传递大文件时,我们需要分块读写。

        /**
         * 大文件 需要分块复制
         */
        //输入流
        RandomAccessFile in = new RandomAccessFile("src\\study\\NIO\\Test09大文件.zip","r");
        //输出流
        RandomAccessFile out = new RandomAccessFile("src\\study\\NIO\\Test09复制大文件.zip","rw");
        //获取通道
        FileChannel inChannel = in.getChannel();
        FileChannel outChannel = out.getChannel();
        //文件总大小
        long size = inChannel.size();//字节
        //每次最多读取多少
        long every = 500*1024*1024;//500MB
        //需要读取几次
        int count = (int) Math.ceil(size*1.0/every);
        //每次从哪里开始读
        long start;
        //实际读取多少
        long actual;
        //定义缓冲区
        MappedByteBuffer inBuffer;
        MappedByteBuffer outBuffer;
        //循环读取
        for (int i = 0; i < count; i++) {
            start = i * every;
            if (i == count -1) {
                actual = size - start;
            } else {
                actual = every;
            }
            inBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY,start,actual);
            outBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE,start,actual);
        }

        //关流
        in.close();
        out.close();
        inChannel.close();
        outChannel.close();

2.5网络编程收发信息

        服务端:

        //服务端
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(9999));
        //获取连接
        SocketChannel accept = serverSocketChannel.accept();
        //缓冲区数组
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //读取数据
        int read = accept.read(buffer);
        //打印
        System.out.println(new String(buffer.array(),0,read));
        //关流
        serverSocketChannel.close();
        accept.close();

        客户端:

        //客户端
        SocketChannel socketChannel = SocketChannel.open();
        //连接服务器端口
        socketChannel.connect(new InetSocketAddress("localhost", 9999));
        //创建数组
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("HelloWorld".getBytes());
        //切换
        buffer.flip();
        //发送
        socketChannel.write(buffer);
        //关流
        socketChannel.close();

2.6accept阻塞

        accept方法是阻塞方法,调用accept方法时,如果没有连接,则不会往下进行。

        设置非阻塞:

        //服务端
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //绑定端口
        ssc.bind(new InetSocketAddress(9999));
        //设置非阻塞
        ssc.configureBlocking(false);
        while (true) {
            //获取连接
            SocketChannel accept = ssc.accept();
            if (accept != null){
                //有连接了
                //创建缓冲数组
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //接收信息
                int read = accept.read(buffer);
                System.out.println(new String(buffer.array(),0,read));
                break;
            }else {
                //没有连接
                System.out.println("没人连我");
            }

        }

3Selector选择器

3.1多路复用概念

        多路复用就是把多个服务器端口交给同一个Selector去管理。

3.2 Selector介绍

        Selector是一个选择器,可以用一个线程处理多个事件。可以注册到多个Channel上,帮多个Channel去处理事件。用一个线程处理了之前多个线程的事务,就给系统减轻负担,提高效率。

select()        :Selector的连接方法,代替之前的accept()方法等待客户端的连接
                阻塞问题:
                    在没有客户端连接的时候,是阻塞状态
                    在有客户端连接并还没有处理的时候,是非阻塞状态
                    在客户处理结束之后,又会恢复到阻塞状态
                    
selectedKeys()    :获取一个集合,被连接的服务端对象会被放在集合中

keys()            :获取一个集合,用于存放被Selector管理的服务对象

3.3Selector选择器的使用

        
        //客户端
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //绑定端口
        ssc.bind(new InetSocketAddress(9999));
        //设置非阻塞
        ssc.configureBlocking(false);
        //创建Selector
        Selector selector = Selector.open();
        //把selector注册到channel上,并把accept这个方法交给Selector去管理
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        //会把被连接的服务对象放在集合中
        Set<SelectionKey> selectionKeys= selector.selectionKeys();

        while (true) {
            //等待连接
            System.out.println("等待连接");
            selector.select();
            System.out.println("连接了");
            for (SelectionKey key : selectionKeys) {
                //获取被连接的对象
                ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                //获取客户端对象
                SocketChannel accept = channel.accept();
                //创建缓冲数组
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //接受数据
                int read = accept.read(buffer);
                System.out.println(new String(buffer.array(),0,read));
                accept.close();
            }

        }

3.4Selector管理多个服务端

        //创建Selector
        Selector selector = Selector.open();
        //客户端
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //绑定端口
        ssc.bind(new InetSocketAddress(9999));
        //客户端
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        //绑定端口
        ssc2.bind(new InetSocketAddress(8888));

        //设置非阻塞
        ssc.configureBlocking(false);
        ssc2.configureBlocking(false);
        //把selector注册到channel上,并把accept这个方法交给Selector去管理
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);
        //会把被连接的服务对象放在集合中
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        //被管理的客户端对象
        Set<SelectionKey> keys = selector.keys();
        System.out.println("被连接的"+selectionKeys);
        System.out.println("被管理的"+keys);
        while (true) {
            //等待连接
            selector.select();
            System.out.println("被连接的"+selectionKeys);
            System.out.println("被管理的"+keys);
            //for (SelectionKey key : selectionKeys) {//被连接的对象放进去没有删掉,所以我们要删掉集合中删除元素要使用迭代器删除
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key =  iterator.next();
                //获取被连接的对象
                ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                //获取客户端对象
                SocketChannel accept = channel.accept();

                //创建缓冲数组
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //接受数据
                int read = accept.read(buffer);
                System.out.println(new String(buffer.array(),0,read));
                accept.close();
                //删除已经操作完的服务端对象
                iterator.remove();
            }
        }

        以上就是部分NIO的知识点啦,后续会继续补充。各位大佬如发现有知识点错误或者有不同的建议与意见,欢迎评论、私信指正,本人才疏学浅还需向各位大佬学习,还请不吝赐教!在此感谢各位的观看!谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值