NIO技术

什么是NIO?

java 的NIO是从jdk1.4引入的一个新的I/O操作的api。可以替换标准的I/O API,NIO与原来的IO具有同样的功能和目的,但是使用方式完全不一样,NIO是面向缓冲区,基于通道的IO操作,NIO将以更加高效的方式进行文件的读写操作。


传统的IO操作是面向流的。
如果是本地文件,那么需要设置接收文件的输入流,同时指定发送文件的输出流,由输出流向输入流通过流式操作发送数据,从而起到复制文件的作用。
如果是网络传输的话,那么服务端需要阻塞接收客户端的流,如果有流进来就将accept的结果再通过获取流的API(getInputStream或者getOutputStream)操作获取流,然后再读取数据。如果没有流进来,此时服务端一直处于阻塞状态,导致服务端无法进行其他的操作。
在这里插入图片描述

面向缓冲区基于通道的NIO是如何进行上述操作的。
对于本地文件来说,NIO通过各种各样的缓冲区比如(ByteBuffer,IntegerBuffer,FloatBuffer,DoubleBuffer,CharBuffer… 除了Boolean类型都有对应的缓冲区)等缓冲区,通过allocate或者allocateDirect获取指定大小的缓冲区,同时再获取各种流式操作对应的管道(FileChannel,SocketChannel,ServerSocketChannel,DatagramChannel),这些通道都集成了读写操作的API,使我们不必要再创建输出流和输出流的操作,我们可以向缓冲区中写入数据,然后将缓冲区交给通道进行传送,我们也可以通过通道从将缓冲区中的数据读取出来。
在这里插入图片描述
简言之: 管道负责连接,缓冲区负责传输数据

缓冲区是如何进行读写

核心方法:
get()获取缓冲区的数据
put()存数据到缓冲区
核心属性:
mark:标记如果调用mark() API的话会存储当前操作数据的位置
position: 当前正在操作数据的位置
limit 表示缓冲区可以操作数据的大小(limit位置之后的位置不能进行读写)
capacity 缓冲区的最大容量

String message = "hello world";
ByteBuffer buf =ByteBuffer.allocate(1024);//设置容量
buf.put(((java.lang.String) message).getBytes());//向缓冲区中写入数据
buf.flip();//将缓冲区切换到读模式
byte[] dst = new byte[buf.limit()];//缓冲取可以操作数据的大小
buf.get(dst,0,3);//将buf部分数据存储到dst数组0-3位置的数据
buf.mark();//标记下当前操作的位置   3
String msg = new String(dst,0,3);//转化为字符串
System.out.println(msg);
System.out.println(buf.position());  //如果position的值为字符串的长度的话  就无法再在缓冲区进行读写了
buf.get(dst,3,3);
System.out.println(new String(dst,3,3));
System.out.println(buf.position());

buf.reset();//回复到mark的位置
System.out.println(buf.position());

在这里插入图片描述

直接缓冲区和非直接缓冲区

非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中。
假如要从磁盘中读取文件,数据不能直接传输到用户地址空间,首先传输到内核地址空间,再从内核地址空间copy到用户地址空间。
在这里插入图片描述
直接缓冲区: 通过allocateDirect 分配直接缓冲区,将缓冲区建立在操作系统的物理内存中。效率高。
弊端,分配物理内存和销毁物理内存消耗资源高。销毁或者写到物理磁盘中由操作系统决定。

在这里插入图片描述

通道技术

主要通道实现类FileChannel,ServerSocketChannel SocketChannel DatagramChannel
各种流基本上提供了通道的获取api(getChannel)
本地IO
FileInputStream/FileOutputStream
RandomAccessFile
获取通道的三种方式之一

 public static void  channel(){
        //利用通道完成文件的复制
        try {
            FileInputStream fileInputStream = new FileInputStream("1.jpg");
            FileOutputStream fileOutputStream = new FileOutputStream("2.jpg");

            //获取通道
            FileChannel channelIn = fileInputStream.getChannel();//通道1
            FileChannel channelOut = fileOutputStream.getChannel();//通道2

            ByteBuffer buf = ByteBuffer.allocate(1024);//缓冲区
            while (channelIn.read(buf)!=-1){//将通道内的数据写入到buf中
                buf.flip();//开始读缓存  不然无法进行读操作因为limit在限制着
                channelOut.write(buf);//将缓存中的内容写到通道中
                buf.clear();//清空缓存
            }
            channelOut.close();
            channelIn.close();
            fileOutputStream.close();
            fileOutputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

通过通道复制文件的三种方式之二: 通过open方法获取通道 通过MappedBytedBuffer 获取映射文件

  //直接映射文件复制文件
public static void  channelMap() throws IOException {
    FileChannel fileChannelIn = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);//从本地文件中直接读取
    FileChannel fileChannelOut = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);//从本地文件中直接读取  在操作文件的时候表明操作的类型

    //内存映射文件
    MappedByteBuffer mapIn = fileChannelIn.map(FileChannel.MapMode.READ_ONLY, 0, fileChannelIn.size());
    MappedByteBuffer mapOut = fileChannelOut.map(FileChannel.MapMode.READ_WRITE, 0, fileChannelIn.size());

    //直接对缓冲区进行数据的读写
    byte[] dst = new  byte[mapIn.limit()];
    mapIn.get(dst);//从缓冲区获取
    mapOut.put(dst);//写入到物理内存中 但是神魔时候写入硬盘 由操作系统决定。
}

通过通道复制文件的三种方式之三:

  public static void test04() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"),StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"),StandardOpenOption.WRITE);

//        inChannel.transferTo(0,inChannel.size(),outChannel);//到哪去
        outChannel.transferFrom(inChannel,0,inChannel.size());//从哪来
        inChannel.close();
        outChannel.close();
    }

网络IO

  • 阻塞式
    Socket
    ServerSocket
    阻塞式的网络IO通过管道和缓冲区,可以让我们省去创建输入流和输出流,即客户端通过获取管道并且向管道里面的缓冲区中写入内容,交给服务端,服务端阻塞接收,然后再通过管道再将反馈的信息写入到缓冲区 发送个客户端,逻辑变得异常清晰。

  • 非阻塞式
    NIO的非阻塞模式管理客户端和服务端的数据传输主要是通过selector选择器 ,要明白为什么使用选择器首先弄明白非阻塞解决的的问题是神魔。
    非阻塞式要解决的问题是针对服务端秩序阻塞接收信息导致cpu利用率低的问题,
    客户端发起对服务端的请求,首先客户端的管道会被注册到selector上,服务端会轮询的检测选择器的状态,
    如果发现注册的客户端有连接请求就注册到选择器上,
    如果注册的客户端有发送数据的请求,就接收数据进行操作,
    即选择器会检测客户端channel的状态(OP_ACCEPT,OP_READ,OP_WRITE,OP_CONNECT)
    在这里插入图片描述
    服务端的操作

public static void server() throws IOException {
        //获取通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);//设置为非阻塞式
        //绑定接口
        serverSocketChannel.bind(new InetSocketAddress(9000));
        //获取选择器
        Selector selector = Selector.open();
        //将通道注册到选择器上  并且指定监听事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//监听连接状态
        //选择器轮询式获取连接器上准备就绪的事件
        while (selector.select()>0){
            Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
            while(selectionKeys.hasNext()){
                SelectionKey selectionKey =  selectionKeys.next();//获取就绪的事件
                if(selectionKey.isAcceptable()){//如果接收就绪就获取连接
                    SocketChannel accept = serverSocketChannel.accept();
                    accept.configureBlocking(false);
                    //将accept注册到selector上  并且监听读操作
                    accept.register(selector,SelectionKey.OP_READ);
                }else if(selectionKey.isReadable()){
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int len =0;
                    while((len = socketChannel.read(byteBuffer))>0){
                        byteBuffer.flip();
                        System.out.println(new String(byteBuffer.array(),0,len));
                        byteBuffer.clear();//清除缓存
                    }
                }	
                selectionKeys.remove();
            }
        }

    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值