总结之NIO编程——文件NIO与网络NIO

简介

java.nio全称java non-blocking IO(实际上是 new io),是指JDK 1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。

HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。

IO和NIO的区别

原有的 IO 是面向流的、阻塞的,NIO 则是面向块的、非阻塞的。

IO是面向流的、阻塞的

java1.4以前的io模型,一连接对一个线程。

原始的IO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区

Java IO的各种流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。

NIO 是面向块的、非阻塞的。

NIO是面向缓冲区的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性。

Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。

NIO的核心实现

在标准IO API中,你可以操作字节流和字符流,但在新IO中,你可以操作通道和缓冲,数据总是从通道被读取到缓冲中或者从缓冲写入到通道中。

NIO核心API Channel, Buffer, Selector

通道Channel

NIO的通道类似于流,但有些区别如下:

  1. 通道可以同时进行读写,而流只能读或者只能写

  2. 通道可以实现异步读写数据

  3. 通道可以从缓冲读数据,也可以写数据到缓冲:

缓存Buffer

缓冲区本质上是一个可以写入数据的内存块,然后可以再次读取,该对象提供了一组方法,可以更轻松地使用内存块,使用缓冲区读取和写入数据通常遵循以下四个步骤:

  1. 写数据到缓冲区;

  2. 调用buffer.flip()方法;

  3. 从缓冲区中读取数据;

  4. 调用buffer.clear()或buffer.compat()方法;

当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,在读模式下可以读取之前写入到buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。

Buffer在与Channel交互时,需要一些标志:

buffer的大小/容量 - Capacity

作为一个内存块,Buffer有一个固定的大小值,用参数capacity表示。

当前读/写的位置 - Position​

当写数据到缓冲时,position表示当前待写入的位置,position最大可为capacity – 1;当从缓冲读取数据时,position表示从当前位置读取。

信息末尾的位置 - limit

在写模式下,缓冲区的limit表示你最多能往Buffer里写多少数据; 写模式下,limit等于Buffer的capacity,意味着你还能从缓冲区获取多少数据。

缓冲区常用的操作

向缓冲区写数据:

1. 从Channel写到Buffer;

2. 通过Buffer的put方法写到Buffer中;

从缓冲区读取数据:

1. 从Buffer中读取数据到Channel;

2. 通过Buffer的get方法从Buffer中读取数据;

flip方法:

将Buffer从写模式切换到读模式,将position值重置为0,limit的值设置为之前position的值;

clear方法 vs compact方法:

clear方法清空缓冲区;compact方法只会清空已读取的数据,而还未读取的数据继续保存在Buffer中;

Selector
一个组件,可以检测多个NIO channel,看看读或者写事件是否就绪。

多个Channel以事件的方式可以注册到同一个Selector,从而达到用一个线程处理多个请求成为可能。
总结之NIO编程——文件NIO与网络NIO_第1张图片

文件NIO

文件写入

//1、创建输出流
        FileOutputStream fos=new FileOutputStream("name.txt");
        //2、从流中得到一个通道
        FileChannel fc=fos.getChannel();
        //3、提供一个缓冲区
        ByteBuffer buffer =ByteBuffer.allocate(1024);
        //4、往缓冲区中存入数据
        String str="hello,nio";
        buffer.put(str.getBytes());
        //5、翻转缓冲区
        buffer.flip();
        //6、把缓冲区写到通道中
        fc.write(buffer);
        //7、关闭
        fos.close();

文件读取

 File file = new File("name.txt");
        //1、创建输入流
        FileInputStream fis=new FileInputStream(file);
        //2、从流中得到一个通道
        FileChannel fc=fis.getChannel();
        //3、提供一个缓冲区
        ByteBuffer buffer =ByteBuffer.allocate((int)file.length());
        //4、从通道里读取数据并存到缓冲区中
        fc.read(buffer);
        System.out.println(new String(buffer.array()));
        //7、关闭
        fis.close();

文件复制

  //1、创建两个流
        FileInputStream fis=new FileInputStream("name.txt");
        FileOutputStream fos=new FileOutputStream("newName.txt");

        //2、得到两个通道
        FileChannel sourceFC =fis.getChannel();
        FileChannel destFC=fos.getChannel();

        //3、复制
        destFC.transferFrom(sourceFC,0,sourceFC.size());

        //4、关闭
        fis.close();
        fos.close();

网络NIO

通过案例来了解网络NIO用法
网络服务端程序

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * @author liuzonghua
 * @Package top.maniy.util.nio
 * @Description: 网络服务端程序
 * @date 2020/4/26 21:45
 */
public class NIOServer {
    public static void main(String[] args) throws Exception{
        //1.得到一个ServerSocketChannel对象
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //2.得到一个Selector对象
        Selector selector = Selector.open();

        //3.绑定一个端口号
        serverSocketChannel.bind(new InetSocketAddress(9999));

        //4.设置非阻塞方式
        serverSocketChannel.configureBlocking(false);

        //5.把serverSocketChannel对象注册给Selector对象,监控客户端
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //6,处理事情
        while(true){
            //6.1监控客户端
            if(selector.select(2000) == 0) {//监控客户端超时时间,0个客户端
                System.out.println("同时可以做其他事情。");
                continue;
            }

            //6.2得到SelectionKey,判断通道里的事件
            Iterator keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()){
                SelectionKey key =keyIterator.next();

                if(key.isAcceptable()){ //客户端连接事件
                    System.out.println("OP_ACCEPT");
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));//重点监控读事件,第三公个是附件
                }

                if (key.isReadable()){ //读取客户端数据事件
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = (ByteBuffer) key.attachment(); //附件为缓存区
                    channel.read(buffer);
                    System.out.println("客户端发来数据:"+new String(buffer.array()));
                }
                //6.3 手动从集合中移除当前key,防治重复处理
                keyIterator.remove();
            }


        }
    }
}

网络客户端程序

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @author liuzonghua
 * @Package top.maniy.util.nio
 * @Description:网络客户端程序
 * @date 2020/4/26 21:32
 */
public class NIOClient {
    public static void main(String[] args) throws Exception {
        //1、得到一个网络通道
        SocketChannel channel = SocketChannel.open();

        //2、设置非阻塞方式
        channel.configureBlocking(false);

        //3、提供服务器的IP地址和端口号
        InetSocketAddress address = new InetSocketAddress("127.0.0.1",9999);

        //4、连接服务器端
        if(!channel.connect(address)){//初次连接失败
            while (!channel.finishConnect()){//一直连接到,连接成功
                System.out.println("连接的同时可以做其他事情。");

            }
        }
        //5.得到一个缓存区并存入数据
        String msg = "info";
        ByteBuffer writeBuf = ByteBuffer.wrap(msg.getBytes());

        //6.发送数据
        channel.write(writeBuf);

        System.in.read();
    }
}

NIO2(AIO)

AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。

但是对AIO来说,则更加进了一步,它不是在IO准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。因此AIO是不会阻塞的,此时我们的业务逻辑将变成一个回调函数,等待IO操作完成后,由系统自动触发。

与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2。

你可能感兴趣的:(IO/NIO)