Java中的NIO是什么

发布时间:2021-07-06 10:25:01 作者:chen
来源:亿速云 阅读:192

这篇文章主要介绍“Java中的NIO是什么”,在日常操作中,相信很多人在Java中的NIO是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中的NIO是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

什么是NIO

  NIO即NEW I/O,是在JDK1.4引入的一套新的I/O标准。NIO是基于Block,以块为基本单元处理数据。并且为所有的基础类型提供了Buffer支持。    数据从Channel中读到Buffer中去,或者从Buffer写入Channel中。Selector监听多个Channel的事件,比如打开链接或数据到达。

graph LR
  A((Channel))--读取-->B[Buffer]
graph LR
  A[Buffer]--写入-->B((Channel))

相比较传统I/O的区别

1、传统的I/O基于字节流和字符流的操作,NIO则是基于Channel和Buffer进行数据操作。 2、IO是面向流的,NIO是面向Buffer缓冲区的。 面向流意味着读取数据从流中挨个读取字节,直至读完全部字节。如果需要对读取的数据进行前后移动操作,则需要建立缓冲区。而NIO的Buffer正好做到这一点,数据从Channel被读取到缓冲区后,可以很方便的的操作数据。 3、IO操作是阻塞模式,调用read()或者write()操作时,线程阻塞,直至数据的读或写全部结束。NIO是非阻塞模式,从Channel读取数据时,如果没有可用数据,就什么都不获取,不会阻塞线程等待,而是把空闲时间用来执行其他Channel上的IO操作,所以单线程可以管理多个Channel。

NIO三大核心部分

Channel(通道) Buffer(缓冲区) Selector(选择区)

Channel

简称“信道”或“通道”,跟IO流里的Stream一个级别,不过Stream是单向的,Channel是双向的,所以既可以用来读操作,也可以用来写操作。 主要实现:

FileChannel

示例: 传统IO

    /**
     * 传统IO操作读取文件
     */
    public static void oldIo(){
        InputStream inputStream = null;
        try{
            inputStream = new BufferedInputStream(
                    new FileInputStream("src\\main\\java\\top\\qrainly\\demo\\nio\\OldIO.txt"));
            byte[] bytes = new byte[1024];
            int read = inputStream.read(bytes);
            System.out.println(read);
            while (read!=-1){
                for(int i=0;i<read;i++){
                    System.out.println((char) bytes[i]);
                }
                read = inputStream.read(bytes);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(inputStream != null){
                    inputStream.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

NIO

/**
     * NIO操作
     */
    public static void nio(){
        RandomAccessFile file = null;
        try{
            file = new RandomAccessFile("src\\main\\java\\top\\qrainly\\demo\\nio\\NIO.txt","rw");
            FileChannel fileChannel = file.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            int read = fileChannel.read(buffer);
            System.out.println(read);
            while (read != -1){
                buffer.flip();
                while (buffer.hasRemaining()){
                    System.out.println((char) buffer.get());
                }
                buffer.compact();
                read = fileChannel.read(buffer);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(file != null){
                    file.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

NIO拷贝文件

/**
     * NIO拷贝文件
     * @param source
     * @param target
     */
    public static void nioCopyFile(String source,String target){
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        FileChannel readChannel = null;
        FileChannel writeChannel = null;
        try {
            fileInputStream = new FileInputStream(source);
            fileOutputStream = new FileOutputStream(target);
            readChannel = fileInputStream.getChannel();
            writeChannel = fileOutputStream.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while (true){
                buffer.clear();
                int read = readChannel.read(buffer);
                if(read == -1){
                    //读取完毕
                    break;
                }
                buffer.flip();
                //写入文件
                writeChannel.write(buffer);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(readChannel != null){
                    readChannel.close();
                }
                if(writeChannel != null){
                    writeChannel.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

SocketChannel

套接字的某些操作可能会无限期地阻塞 对accept()方法的调用可能会因为等待一个客户端连接而阻塞; 对read()方法的调用可能会因为没有数据可读而阻塞,直到连接的另一端传来新的数据。 NIO的channel抽象的一个重要特征就是可以通过配置它的阻塞行为,以实现非阻塞式的信道

channel.configureBlocking(false)

示例:

/**
     * 客户端 NIO实现
     */
    public static void client(){
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        SocketChannel socketChannel = null;
        try{
            //打开SocketChannel
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("localhost",8080));
            if(socketChannel.finishConnect()){
                int i = 0;
                while (true){
                    TimeUnit.SECONDS.sleep(1);
                    //读取数据
                    String info = "I'm "+i+++"-th from client";
                    buffer.clear();
                    buffer.put(info.getBytes());
                    buffer.flip();
                    while (buffer.hasRemaining()){
                        System.out.println(buffer);
                        socketChannel.write(buffer);
                    }
                }
            }
        }catch (IOException | InterruptedException e){
            e.printStackTrace();
        } finally {
            try{
                if(socketChannel != null){
                    //关闭SocketChannel
                    socketChannel.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

服务端NIO实现

private static final int BUF_SIZE = 1024;
    private static final int PORT = 8080;
    private static final int TIME_OUT = 3000;

    public static void selector(){
        Selector selector = null;
        ServerSocketChannel serverSocketChannel = null;
        try{
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true){
                if(selector.select(TIME_OUT) == 0){
                    System.out.println("等待连接...");
                    continue;
                }
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey key= iterator.next();
                    if(key.isAcceptable()){
                        handleAccept(key);
                    }
                    if(key.isReadable()){
                        handleRead(key);
                    }
                    if(key.isWritable() && key.isValid()){
                        handleWrite(key);
                    }
                    if(key.isConnectable()){
                        System.out.println("连接成功!");
                    }
                    iterator.remove();
                }
            }

        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(selector != null){
                    selector.close();
                }
                if(serverSocketChannel != null){
                    serverSocketChannel.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 处理连接
     * @param key
     * @throws IOException
     */
    public static void handleAccept(SelectionKey key) throws IOException {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        //监听连接
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(key.selector(),SelectionKey.OP_READ, ByteBuffer.allocateDirect(BUF_SIZE));
    }

    /**
     * 处理读
     * @param key
     * @throws IOException
     */
    public static void handleRead(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        long bytesRead = socketChannel.read(buffer);
        while (bytesRead > 0){
            buffer.flip();
            while (buffer.hasRemaining()){
                System.out.println((char) buffer.get());
            }
            buffer.clear();
            bytesRead = socketChannel.read(buffer);
        }
        if(bytesRead == -1){
            socketChannel.close();
        }
    }

    /**
     * 处理写
     * @param key
     * @throws IOException
     */
    public static void handleWrite(SelectionKey key) throws IOException {
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        buffer.flip();
        SocketChannel socketChannel = (SocketChannel) key.channel();
        while (buffer.hasRemaining()){
            socketChannel.write(buffer);
        }
        buffer.compact();
    }

  Selector类可以用于避免使用阻塞式客户端中很浪费资源的“忙等”方法需要读取和分发。这就需要一种方法阻塞等待,直到至少有一个信道可以进行I/O操作,并指出是哪个信道。NIO的选择器就实现了这样的功能。 一个Selector实例可以同时检查一组信道的I/O状态。用专业术语来说,选择器就是一个多路开关选择器,因为一个选择器能够管理多个信道上的I/O操作。 然而如果用传统的方式来处理这么多客户端,使用的方法是循环地一个一个地去检查所有的客户端是否有I/O操作,如果当前客户端有I/O操作,则可能把当前客户端扔给一个线程池去处理,如果没有I/O操作则进行下一个轮询,当所有的客户端都轮询过了又接着从头开始轮询;这种方法是非常笨而且也非常浪费资源,因为大部分客户端是没有I/O操作,我们也要去检查;而Selector就不一样了,它在内部可以同时管理多个I/O,当一个信道有I/O操作的时候,他会通知Selector,Selector就是记住这个信道有I/O操作,并且知道是何种I/O操作,是读呢?是写呢? 还是接受新的连接;   使用Selector,它返回的结果只有两种结果,一种是0,即在你调用的时刻没有任何客户端需要I/O操作,另一种结果是一组需要I/O操作的客户端,这时你就根本不需要再检查了,因为它返回给你的肯定是你想要的。 这样一种通知的方式比那种主动轮询的方式要高效得多!

  与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用, 因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。

Selector

  异步网络IO Selector运行单线程处理多个Channel,如果应用打开了多信道,单个连接流量很低,此时使用Selector就会很方便。向Selector注册Channel,然后条用select()方法,这个方法会一直阻塞到有注册的信道有事件就绪,例如连接打开或者数据到达。   要使用选择器(Selector),需要创建一个Selector实例(使用静态工厂方法open())并将其注册(register)到想要监控的信道上(注意,这要通过channel的方法实现,而不是使用selector的方法)。最后,调用选择器的select()方法。该方法会阻塞等待,直到有一个或更多的信道准备好了I/O操作或等待超时。select()方法将返回可进行I/O操作的信道数量。现在,在一个单独的线程中, 通过调用select()方法就能检查多个信道是否准备好进行I/O操作。如果经过一段时间后仍然没有信道准备好,select()方法就会返回0, 并允许程序继续执行其他任务。

Buffer

Buffer中有四个个重要的参数:位置(position)、容量(capactiy)、上限(limit)和标记(mark)

参数写模式写模式
位置(position)当前缓冲区的位置,将从position的下一个位置开始写数据当前缓冲区读取的位置,将从position下一个位置
容量(capactiy)缓冲区的总容量上限缓冲区的总容量上限
上限(limit)缓冲区的时机上限,limit<=capactiy代表可读取的总容量,和上次写入的数据量相等
标记(mark)用于记录position前一个位置用于记录position前一个位置

使用步骤

  1. 分配空间(ByteBuffer buf = ByteBuffer.allocate(1024); 或者allocateDirector)

  2. 写入数据到Buffer(int bytesRead = fileChannel.read(buf);)

  3. 调用filp()方法( buf.flip();)

  4. 从Buffer中读取数据(System.out.print((char)buf.get());)

  5. 调用clear()方法或者compact()方法 Buffer顾名思义:缓冲区,实际上是一个容器,一个连续数组。Channel提供从文件、网络读取数据的渠道,但是读写的数据都必须经过Buffer。 从Channel写到Buffer (fileChannel.read(buf))通过Buffer的put()方法 (buf.put(…)) 从Buffer读取到Channel (channel.write(buf))使用get()方法从Buffer中读取数据 (buf.get())

操作示例

1、ByteBuffer.allocate(10)方法创建了一个10个byte的数组缓冲区,position的位置为0,capacity和limit默认都是数组长度 2、当我们写入5个字节时,position变为5,limit和capacity不变 3、将缓冲区中的5个字节数据写入Channel的通信信道,调用ByteBuffer.flip()方法,position变为0,limit变为5,capactiy不变 4、下一次写入数据前调用clear()方法 -- 调用clear()方法:position将被设回0,limit设置成capacity(Buffer中的数据并未被清除) -- 调用compact()方法。compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。 limit属性依然像clear()方法一样,设置成capacity。不会覆盖未读的数据。 5、调用Buffer.mark()方法,可以标记Buffer中的一个特定的position,之后可以通过调用Buffer.reset()方法恢复到这个position。 Buffer.rewind()方法将position设回0,并清除标记位,所以可以重读Buffer中的所有数据。limit保持不变, 仍然表示能从Buffer中读取多少个元素 代码示例

/**
     * Buffer操作过程  参数变化
     */
    public static void bufferParams(){
        //15个字节大小的缓冲区
        ByteBuffer b=ByteBuffer.allocate(15);
        System.out.println("########创建15个字节的数组缓冲区########");
        System.out.println("limit="+b.limit()+" capacity="+b.capacity()+" position="+b.position());
        //存入10个字节数据
        System.out.println("########开始存入十个字节数据########");
        for(int i=0;i<10;i++){
            b.put((byte)i);
            System.out.println("########存入-->"+i+"########");
        }
        System.out.println("########存入十个字节数据完成########");
        System.out.println("limit="+b.limit()+" capacity="+b.capacity()+" position="+b.position());
        //重置position
        System.out.println("########调用flip()方法########");
        b.flip();
        System.out.println("########调用完flip()方法后########");
        System.out.println("limit="+b.limit()+" capacity="+b.capacity()+" position="+b.position());
        System.out.println("########开始读取五个字节数据########");
        for(int i=0;i<5;i++){
            byte b1 = b.get();
            System.out.print(b1);
            System.out.println("########读取字节数据-->"+b1+"########");
        }
        System.out.println("########读取五个字节数据完成########");
        System.out.println(); System.out.println("limit="+b.limit()+" capacity="+b.capacity()+" position="+b.position());
        System.out.println("########调用flip()方法########");
        b.flip();
        System.out.println("########调用完flip()方法后########");
        System.out.println("limit="+b.limit()+" capacity="+b.capacity()+" position="+b.position());
    }

打印结果

########创建15个字节的数组缓冲区########
limit=15 capacity=15 position=0
########开始存入十个字节数据########
########存入-->0########
########存入-->1########
########存入-->2########
########存入-->3########
########存入-->4########
########存入-->5########
########存入-->6########
########存入-->7########
########存入-->8########
########存入-->9########
########存入十个字节数据完成########
limit=15 capacity=15 position=10
########调用flip()方法########
########调用完flip()方法后########
limit=10 capacity=15 position=0
########开始读取五个字节数据########
0########读取字节数据-->0########
1########读取字节数据-->1########
2########读取字节数据-->2########
3########读取字节数据-->3########
4########读取字节数据-->4########
########读取五个字节数据完成########

limit=10 capacity=15 position=5
########调用flip()方法########
########调用完flip()方法后########
limit=5 capacity=15 position=0

三个重要的方法

1、rewind() – 将position置零,并清除标志位(mark 2、clear() – 将position置零,同时将limit设置为capacity的大小,并清除了标志mark 3、flip() – 先将limit设置到position所在位置,然后将position置零,并清除标志位mark – 通常在读写转换时使用

文件映射到内存

示例:

/**
     * 文件映射到内存
     */
    public static void mapperMemory(){
        RandomAccessFile randomAccessFile = null;
        try{
            randomAccessFile = new RandomAccessFile(
                    "src\\main\\java\\top\\qrainly\\demo\\nio\\NIO.txt","rw");
            FileChannel fileChannel = randomAccessFile.getChannel();
            //将文件映射到内存中
            MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE,
                    0,randomAccessFile.length());
            while (mappedByteBuffer.hasRemaining()){
                System.out.println((char) mappedByteBuffer.get());
            }
            //修改文件
            mappedByteBuffer.put(0,(byte)98);
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(randomAccessFile != null){
                    randomAccessFile.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }

    }

到此,关于“Java中的NIO是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. java中nio和io的区别是什么
  2. java中的NIO介绍

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:小程序如何实现rich-text对富文本支持

下一篇:Maven项目中jar冲突如何解决

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》