Java8新特性——NIO

NIO

一、NIO

1.1 概述

NIO全程Java Non-blocking IO或Java New IO,是从JDK1.4开始引入的一套全新的IO,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络

  • BIO:阻塞式编程
  • NIO:非阻塞式编程
  • AIO(NIO2) Asynchronous:非阻塞异步网络编程

IO操作模式

  • PIO(Programing IO)
    • 所有的IO操作由NICPU处理,CPU占用率比较高
  • DMA(Direct Memory Access)
    • CPU把IO操作控制权交给DMA控制,只能以固定的方式读写,CPU空闲做其他工作
  • 通道方式(Channel)
    • 能执行优先通道指令的IO控制器,代替CPU管理控制外设
    • 通道由自己的指令系统,式一个协处理器,具有更强的独立处理数据输入和输出的能力

1.2 NIO组成

  • Buffer:缓冲区
  • Channel:通道
  • Selector:选择器(轮询器)

NIO和普通IO的区别

普通IONIO
面向流(Stream Oriented)面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO)非阻塞IO(Non Blocking IO)
选择器(Selector)
1.2.1 Buffer缓冲区
  • Java NIO中的Buffer用于和NIO通道进行交互
  • 缓冲区本质上是一块可以写入数据,也可以从中读取数据的内存
  • Buffer是一个抽象类,子类包括:
    • ByteBuffer
    • CharBuffer
    • DoubleBuffer
    • FloatBuffer
    • IntBuffer
    • LongBuffer
    • ShortBuffer
  • 由于子类也是抽象类,所以不能实例化对象

Buffer基本使用

  • 创建缓冲区
    • 间接缓冲区
      • 在堆中开辟,易于管理,垃圾回收器可以回收,空间有限,读写文件速度较慢
    • 直接缓冲区
      • 直接在物理内存中开辟空间,空间比较大,读写文件速度快
      • 缺点:不受垃圾回收器控制,创建和销毁耗性能
  • 调用put方法写入数据到Buffer
  • 调用flip()方法
  • 调用get方法从Buffer中读取数据
  • 调用clear()方法或者compact()方法
1.2.2 Buffer核心方法
方法名描述
static ByteBuffer allocate(int capacity)在堆中开辟空间,间接缓冲区
ByteBuffer put(byte[] src)在直接内存开辟空间,直接缓冲区
Buffer flip()flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值
ByteBuffer get(byte[] dst)使用get()方法从Buffer中读取数据
Buffer clear()清空缓冲区,position将被设回0,limit被设置成capacity的值
ByteBuffer compact()compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素
  • Buffer三个重要属性
    • capacity
    • position
    • limit

在这里插入图片描述

1.2.3 Channel通道
  • Channel类似流。数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中
  • Channel接口主要实现类
    • FileChannel
    • ServerSocketChannel
    • SocketChannel
    • DatagramChannel

在这里插入图片描述

1.2.4 FileChannel
  • Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。
  • FileChannel无法设置为非阻塞模式。
  • 创建方式三种
    • 使用文件字节流或RandomAccessFile来获取一个FileChannel实例
    • 使用Channels工具类
    • JDK1.7后才能使用,FileChannel.open()方法
public class CreateFileChannel {
    public static void main(String[] args) throws Exception {
        //方式一:
        FileChannel fileChannel = FileChannel.open(Paths.get("D:\\aa.txt"), StandardOpenOption.READ);

        //方式二:
        FileChannel fileChannel1 = new RandomAccessFile("D:\\aa.txt","rw" ).getChannel();

        //方式三:
        FileChannel fileChannel2 = new FileInputStream("D:\\aa.txt").getChannel();
    }
}
//文件复制
public class CopyFile {
    public static void main(String[] args) throws Exception{
        FileChannel file = new FileInputStream("D:\\aa.txt").getChannel();
        FileChannel file1 = new FileOutputStream("D:\\copyaa.txt").getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(file.read(buffer)>0){
            buffer.flip();
            file1.write(buffer);
            buffer.clear();
        }

        file1.close();
        file.close();
    }
}

//解码器
public class FileChannelDemo {

    public static void main(String[] args) throws Exception{
        //创建FileChannel
//        FileChannel fileChannel = new FileInputStream("D:\\aa.txt",).getChannel();
        FileChannel channel = new RandomAccessFile("D:\\aaa.txt", "r").getChannel();
        //创建解码器
        CharsetDecoder decoder = Charset.forName("utf-8").newDecoder();
        //创建字符传冲区
        CharBuffer charBuffer = CharBuffer.allocate(3);


        ByteBuffer buffer = ByteBuffer.allocate(3);
        System.out.println(""+buffer.limit()+"--->"+buffer.position());
        while(channel.read(buffer)>0){
            buffer.flip();
//            System.out.println("反转"+buffer.limit()+"--->"+buffer.position());
//            System.out.println(new String(buffer.array(),0,buffer.limit(),"UTF-8"));
            decoder.decode(buffer, charBuffer, false);
            charBuffer.flip();
            String data = charBuffer.toString();
            System.out.println(data);

            buffer.compact();
            charBuffer.clear();
//            buffer.clear();
        }
//        channel.close();
    }

}

1.2.4.1 内存映射文件
  • 直接缓冲区的使用,可以提高读写的速度。但是直接缓冲区的创建和销魂的开销比较大,一般大文件操作或能显著提高读写性能时使用
  • 内存映射文件也属于直接缓冲区
public class Demo01 {
    public static void main(String[] args) throws Exception{
        //创建文件通道
        FileChannel in = new RandomAccessFile("D:\\11.jpg", "rw").getChannel();
        FileChannel out = new RandomAccessFile("D:\\ddd11.jpg","rw").getChannel();
        MappedByteBuffer map = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
        out.write(map);

        out.close();
        in.close();
    }
}

1.2 NIO实现网络编程

  • NIO中提供了实现非阻塞式网络编程API

  • ServerSocketChannel

    • ServerSocketChannel是一个基于通道的socket监听器,等同于ServerSocket类
  • SocketChannel

    • SocketChannel是一个基于通道的客户端套接字,等同于Socket类
  • NIO也支持阻塞时网络编程

public class ServerDemo {
    public static void main(String[] args) throws Exception{
        //服务器
        ServerSocketChannel server = ServerSocketChannel.open();
        //绑定端口号,地址
        server.bind(new InetSocketAddress("10.9.62.210",8888));
        //侦听客户端SocketChannel
        SocketChannel channel = server.accept();
        //数据处理
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        buffer.flip();
        String data = new String(buffer.array(), 0, buffer.limit());
        System.out.println(channel.getRemoteAddress()+"说:"+data);

//        buffer.clear();
        buffer.put("你好".getBytes());
        buffer.flip();
        channel.write(buffer);
        //关闭
        buffer.clear();
        server.close();
    }
}
public class ClientDemo {
    public static void main(String[] args) throws Exception{
//        ServerSocketChannel socketChannel = ServerSocketChannel.open();
//        socketChannel.bind(new InetSocketAddress("10.9.62.210",8888));
        //创建SocketChannel
        SocketChannel channel = SocketChannel.open(new InetSocketAddress("10.9.62.210",8888));

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("好久不见".getBytes());
        buffer.flip();
        channel.write(buffer);

        buffer.flip();
        channel.read(buffer);
        buffer.flip();
        String data = new String(buffer.array(),0,buffer.limit());
        System.out.println(channel.getRemoteAddress()+"shuo:"+data);
        //guanbi
        buffer.clear();
        channel.close();
    }
}

二、 Selector

  • Selectot提供了询问通道是否已经准备号执行每个I/O操作的能力
  • Selector允许单线程处理多个Channel
  • 仅用单个线程来处理多个Channel的好处是,只需要更少的线程来处理通道。这样会大量的减少线程上下文切换的开销

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IaatwBbb-1617807085630)(C:\Users\Evan\AppData\Roaming\Typora\typora-user-images\image-20210407213429272.png)]

  • Selector
    • 选择器类管理着一个被注册的通道集合的信息和他们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态
  • SelectorKey
    • 选择键封装了特定的通道与特定的选择器的注册关系
    • 选择键支持四种操作类型
      • SelectionKey.OP_CONNECT
      • SelectionKey.OP_ACCEPT
      • SelectionKey.OP_READ
      • SelectionKey.OP_WRITE
//服务器
public class ChatServer {
    public static void main(String[] args) throws Exception{
        //1.创建ServerSocketChannel
        ServerSocketChannel server = ServerSocketChannel.open();
        //2.绑定IP和端口号
        server.bind(new InetSocketAddress("10.9.62.210", 6666));
        //3.设置为非阻塞模式
        server.configureBlocking(false);
        //4.创建轮询器
        Selector selector = Selector.open();
        //5.在server注册到轮询器上
        server.register(selector, SelectionKey.OP_ACCEPT);
        //6.轮询处理(阻塞方法),返回键的个数
        while(selector.select()>0){
            //7.获取所有选择键
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()){
                SelectionKey key = iterator.next();
                //8.如果有客户连接
                if(key.isAcceptable()){
                    //9.接收请求,不是阻塞方法
                    SocketChannel accept = server.accept();
                    System.out.println(accept.getRemoteAddress()+"进入了聊天室");
                    //10.设置非阻塞模式
                    accept.configureBlocking(false);
                    //11.注册selector
                    accept.register(selector, SelectionKey.OP_READ);
                }else if(key.isReadable()){
                    //读取数据
                    //12.根据key,获取SocketChannel
                    SocketChannel channel = (SocketChannel)key.channel();

                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len;

                    try {
                        while((len=channel.read(buffer))>0){//如果客户端没有正常关闭,出现异常
                            buffer.flip();
                            String data = new String(buffer.array(),0,buffer.limit());
                            System.out.println(channel.getRemoteAddress()+"说:"+data);
                            buffer.clear();
                        }
                        if(len==-1){//正常结束
                            System.out.println(channel.getRemoteAddress()+"退出了聊天室");
                            channel.close();
                        }
                    } catch (IOException e) {
                        System.out.println(channel.getRemoteAddress()+"异常退出了聊天室");
                        channel.close();
                    }
                }
                iterator.remove();
            }

        }

    }
}

//客户端
public class ChatClient {
    public static void main(String[] args) throws Exception{
        //1.创建SocketChannel
        SocketChannel client = SocketChannel.open(new InetSocketAddress("10.9.62.210", 6666));
        //2.设置非阻塞
        client.configureBlocking(false);
        Scanner scanner = new Scanner(System.in);
        //3.处理
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(true){
            String data = scanner.next();
            buffer.put(data.getBytes());
            buffer.flip();
            client.write(buffer);
            buffer.clear();
            if("886".equals(data)||"byebye".equals(data)){
                break;
            }
        }
        buffer.clear();
        client.close();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值