JAVA 基础 day16 NIO

NIO

Java Non-blocking IO或Java New IO,是从JDK1.4 开始引入 的一套新的IO,为所有的原始类型(boolean类型除外)提供缓存支持的 数据容器

使用它可以提供非阻塞式的高伸缩性网络

jdk 1.7后加入AIO(NIO2)
BIO:阻塞IO NIO:非阻塞IO AIO;异步IO
在这里插入图片描述
在这里插入图片描述

Buffer缓冲区

在这里插入图片描述
子类中没有boolean

Buffer基本使用

在这里插入图片描述
allocate()堆中开辟 alloacteDirect()物理内存开辟
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

用于接收的字符数组长度不能大于缓冲区长

public class TestBuffer {
    public static void main(String[] args) {
        // 1.创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);// 实际上就是分配一个数组
        // 2.put方法存放珊瑚橘
        byte[] bytes = "chichi yyds".getBytes();
        buffer.put(bytes);
        // 3.翻转缓冲区(把写入模式转为读读取模式)
        buffer.flip();
        // 4.读取 字符数组长度不能大于缓冲区长
        byte[] bytes1 = new byte[buffer.limit()];
        buffer.get(bytes1);
        System.out.println(new String(bytes1,0,bytes.length));
        // 5.清空
        buffer.clear();
    }
}
Buffer原理

在这里插入图片描述
mark<=position<=limit<=capcity 标记 位置, 限制, 容量

Buffer使用过程

创建后默认为写模式,position初始数组0下标(初始位置),limit与capcity都在末尾,每次写入position后移,flip()后,limit指向此时position的位置,position回到起始位置。变为读模式

清空clear后,三个指针回到缓冲区创建的初始位置(clear并没有删除数据,数据处于遗忘状态,再次存入数据时会覆盖原数据),但是已经读不到数据了(limit限制,只能到达新元素的位置),
使用compact方法可以保存未读数据(此时position~limit之间剩余)到Buffer起始处,position指向最后一个未读元素后边,(未读数据存在)flip()翻转后可以再次读到数据了

mark标记可以理解为一个书签,调用mark()方法可以在此时position打上一个标签,当position后移后,调用reset方法可以把position置为mark位置

Channel通道

在这里插入图片描述

后buffer为模式才能 read方法(从通道中读取,写入到缓冲区中)
在这里插入图片描述
阻塞模式通道的read为阻塞方法,读完自己想要的才会通过
在这里插入图片描述
buffer为读模式才能 write(从缓冲区中读出写到通道中)
在这里插入图片描述
在这里插入图片描述

直接缓冲区不能使用.array()方法返回数组

创建通道的四种方式
1.使用字节流或RandomAccessFile来获取一个FileChannel实例
2.JDK1.7之后才能使用, FileChannel.open()方法
3.使用工具类Channels的newChannel()方法
4.文件字节流

        // 1 使用字节文件流
       RandomAccessFile file = new RandomAccessFile("chichi.text", "rw");
       FileChannel channel = file.getChannel();
        // 2.使用工具类
       FileChannel Channel = (FileChannel)Channels.newChannel(new FileOutputStream("chichi.txt"))
        // 3.open方法 打开方式若为create_new,文件错在会报错
        FileChannel open = FileChannel.open(Paths.get("chichi.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        // 4.文件字节流
FileOutputStream fileOutputStream= new FileOutputStream("d:\\hah.txt");
FileChannel channel=fileOutputStream.getChannel();

channel通道写

public static void write() throws Exception{   
        // 1.2open方法 打开方式若为create_new,文件错在会报错
        FileChannel open = FileChannel.open(Paths.get("chichi.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        ByteBuffer allocate = ByteBuffer.allocate(1024);
        allocate.put("赤赤 永远的神".getBytes());
        //转换模式 写入通道
        allocate.flip();
        open.write(allocate);
        // 关闭 channel关闭会自动关闭缓冲区
        open.close();

    }

channel关闭会自动关闭缓冲区

channel通道读

public static void read() throws Exception{
        //工具类创建通道
//        FileChannel Channel = (FileChannel)Channels.newChannel(new FileInputStream("chichi.txt"));
        FileChannel open = FileChannel.open(Paths.get("chichi.txt"), StandardOpenOption.READ);
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        // 创建解码器 当接收缓冲区空间小,读取汉字时可能会因读不全(三个字节)而产生乱码
        // 英文时无此问题,直接用ByteBuffer读就可以
        // 通过解码器,把ByteBuffer解码成 CharBuffer
        CharsetDecoder charsetDecoder = Charset.forName("utf-8").newDecoder();

        CharBuffer charBuffer = CharBuffer.allocate(10);//大小要有
        //read时把通道中的读到Buffer中,时写模式
        while (open.read(byteBuffer)>0){//读到末尾会返回0,-1,
            byteBuffer.flip();//变写为读
            charsetDecoder.decode(byteBuffer, charBuffer, false);//false默认
            charBuffer.flip();//变写为读
            System.out.println(charBuffer.toString());
            byteBuffer.compact();//可能有没读完的字节,不能clear
            charBuffer.clear();
        }
        open.close();
    }

普通IO操作
在这里插入图片描述

文件大的时候使用直接内存,文件小的时候用间接内存

直接缓冲区的使用,可以提高读写的速度。但是直接缓冲区的创建和销毁的 开销比较大,一般大文件操作或能显著提高读写性能时使用

内存映射

• 内存映射文件也属于直接缓冲区

注意:如果文件超过2G,需要分多个文件映射

		FileChannel readChannel = FileChannel.open(Paths.get("kekeluo.jpg"), StandardOpenOption.READ);
        FileChannel writeChannel = FileChannel.open(Paths.get("chichi.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
        // 直接内存映射文件 文件超过2G,采用多映射
        MappedByteBuffer map=readChannel.map(FileChannel.MapMode.READ_ONLY,0,1);
        MappedByteBuffer map2=readChannel.map(FileChannel.MapMode.READ_ONLY,1, readChannel.size()-1);
        // 内存映射文件写入
        writeChannel.write(map);
        writeChannel.write(map2);
        // 关闭
        writeChannel.close();
        readChannel.close();
        System.out.println("复制完毕");

NIO网络编程(TCP)

应对任务小,高并发
既支持阻塞式又支持非阻塞式
在这里插入图片描述
NIO阻塞式编程

public class Server {
    public static void main(String[] args) throws Exception{
        ServerSocketChannel listener=ServerSocketChannel.open();
        // 绑定地址和端口号
        listener.bind(new InetSocketAddress("10.0.139.195",8888));
        // 监听
        SocketChannel socketChannel = listener.accept();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        socketChannel.read(byteBuffer);
        byteBuffer.flip();
        System.out.println(socketChannel.getRemoteAddress()+new String(byteBuffer.array(),0,byteBuffer.limit()));//array返回数组
        byteBuffer.clear();
        //关闭
        socketChannel.close();
        listener.close();


    }
}

 public static void main(String[] args) throws Exception{
        // 客户端可以直接地址(客户端的)
        SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("10.0.139.195",8888));
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        buffer.put("chichi yyds".getBytes());
        buffer.flip();
        socketChannel.write(buffer);
        buffer.clear();
        socketChannel.close();
    }

Selector

多路复用
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

NIO非阻塞式网络编程

public class Server {
    public static void main(String[] args) throws Exception{
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
        // 绑定地址和端口
        serverSocketChannel.bind(new InetSocketAddress("10.0.139.195", 8888));
        // 设置为非阻塞式
        serverSocketChannel.configureBlocking(false);
        // 创建选择器
        Selector selector=Selector.open();
        // 注册到选择器
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 轮询 select() 阻塞方法,没有时间发送阻塞(选择器中没有位置)
        // 返回已更新其准备就绪操作集的键的数目,该数目可能为零
        System.out.println("客户端启动了");
        while (selector.select()>0){
            //返回可用的键集
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                if(selectionKey.isAcceptable()){
                    //表示有客户端请求
                    SocketChannel socketChannel = serverSocketChannel.accept();//不会阻塞,可能有其他的在运行
                    System.out.println(socketChannel.getRemoteAddress()+"进入聊天室");
                    //设置非阻塞
                    socketChannel.configureBlocking(false);
                    //注册到selector
                    socketChannel.register(selector,SelectionKey.OP_READ);
                }else if(selectionKey.isReadable()){
                    //获取到此键所对应的通道
                    SocketChannel channel = (SocketChannel)selectionKey.channel();
                    ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
                    int len=0;
                    //客户端发送空,len为0
                    //如果某个通道处于阻塞模式,并且缓冲区中至少剩余一个字节,则在读取至少一个字节之前将阻塞此方法
                    try {
                        while ((len=channel.read(byteBuffer))>0){//不会阻塞
                            byteBuffer.flip();
                            System.out.println(channel.getRemoteAddress()+":::"+new String(byteBuffer.array(),0,byteBuffer.limit()));
                            byteBuffer.clear();

                        }
                        if (len==-1){
                            System.out.println(channel.getRemoteAddress()+"退出了聊天室");
                            channel.close();
                        }
                    } catch (IOException e) {
                        System.out.println(channel.getRemoteAddress()+"异常退出了");
                        channel.close();
                    }
                }
                //处理过的键删除
                iterator.remove();
            }
        }
    serverSocketChannel.close();
    }
}



public class Client {
    public static void main(String[] args) throws Exception{
        Scanner scanner = new Scanner(System.in);
        SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("10.0.139.195", 8888));
        socketChannel.configureBlocking(false);
        while (true){
            String data=scanner.nextLine();
            if("886".equals(data)){
                break;
            }
            ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
            byteBuffer.put(data.getBytes());
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
            byteBuffer.clear();
        }
        socketChannel.close();
    }
}

注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。

通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。

一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道
当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。

注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值