NIO

作者: jiahzhon | 来源:发表于2021-03-15 18:26 被阅读0次

    简介

    • NIO与原来的IO有同样的作用和目的,但是使用方式完全不同,NIO支持面向缓存区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。

    java NIO 与 IO的主要区别

    IO NIO
    面向流 面向缓存区
    阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)
    (无) 选择器
    • Channel 负责传输
    • Buffer 负责存储

    缓存区存取数据的核心方法

    • put() 写

    • get() 读

    • flip() 切换模式

    缓存区的四个核心属性

    • private int mark = -1; //标记,表示当前position的位置。可以通过reset()恢复到mark的位置

    • private int position = 0; //位置,表示缓存区正在操作数据的位置。

    • private int limit; //界限,表示缓存区可以操作数据的大小。(limit后数据不能进行读写)

    • private int capacity; //容量,表示缓存区的最大存储数据的容量。一旦声明不能改变。

    • 0 <= mark <= position <= limit <= capacity

    public class test1 {
    
        public static void main(String[] args) {
            test2();
        }
        public static void test2(){
            String str = "abcde";
    
            //1. 分配一个指定大小的缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
    
            System.out.println("----------allocate()---------");
            System.out.println(buf.position());//0
            System.out.println(buf.limit());//1024
            System.out.println(buf.capacity());//1024
    
            //2. 利用put()存入数据到缓冲区中
            buf.put(str.getBytes());
    
            System.out.println("----------put()---------");
            System.out.println(buf.position());//5
            System.out.println(buf.limit());//1024
            System.out.println(buf.capacity());//1024
    
            //3.切换读取数据模式
            buf.flip();
    
            System.out.println("----------put()---------");
            System.out.println(buf.position());//0
            System.out.println(buf.limit());//5
            System.out.println(buf.capacity());//1024
    
            //4.利用get()
            byte[] dst = new byte[buf.limit()];
            buf.get(dst);
            System.out.println(new String(dst,0,dst.length));
    
            System.out.println("----------get()---------");
            System.out.println(buf.position());//5
            System.out.println(buf.limit());//5
            System.out.println(buf.capacity());//1024
    
            //5.可重复读
            buf.rewind();
            System.out.println("----------rewind()---------");
            System.out.println(buf.position());//0
            System.out.println(buf.limit());//5
            System.out.println(buf.capacity());//1024
    
            //6.clear():清空缓冲区,但是缓冲区中的数据依然存在,但是出于“被遗忘”状态
            buf.clear();
            System.out.println("----------clear()---------");
            System.out.println(buf.position());//0
            System.out.println(buf.limit());//1024
            System.out.println(buf.capacity());//1024
    
            //mark:标记,表示记录当前position的位置。可以通过reset()恢复到mark位置
        }
    }
    

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

    非直接缓存区:allocate()方法分配缓存区,将缓存区建立在JVM的内存中(堆,数组)

    直接缓存区:allocateDirect()方法分配直接缓存区,将缓存区建立在物理内存中,可以提高效率

    • 非直接缓冲区


      image.png
    • 直接缓冲区


      image.png

    Channel

    • Channel表示IO源与目标打开的连接。Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。
    • 分类:
      • FileChannel

      • SelectableChannel

        • SocketChannel
        • SockeServerChannel
        • DatagramChannel
        • Pipe.SinkChannel
        • Pipe.SourceChannel
    • 获取通道
    1. Java针对支持通道的类提供了getChannel()方法:
    • 本地IO:
      • FileInputStream/FileOutputStream
      • RamdomAccessFile
    • 网络IO:
      • Socket
      • ServerSocket
      • DatagramSocket
    1. 在JDK 1.7中的NIO.2 针对各个通道提供了静态方法open()
    2. 在JDK 1.7中的NIO.2 的Files工具类的newByteChannel()
    • 利用通道完成文件的复制(非直接缓冲区)
    public class FileCopy {
    
        public static void main(String[] args) {
            test();
        }
    
        public static void test(){
            FileInputStream fis = null;
            FileOutputStream fos = null;
    
            //获取通道
            FileChannel inchannel = null;
            FileChannel outChannel = null;
    
            try {
                fis = new FileInputStream("D:/1.jpg");
                fos = new FileOutputStream("D:/2.jpg");
    
                inchannel = fis.getChannel();
                outChannel = fos.getChannel();
    
                //分配指定大小的缓冲区
                ByteBuffer buf = ByteBuffer.allocate(1024);
    
                //将通道中的数据存入到缓冲区中
                while (inchannel.read(buf) != -1){
                    buf.flip(); //切换到读数据的模式
                    //将缓冲区的数据写入到通道中
                    outChannel.write(buf);
                    buf.clear();//清空缓存区
                }
            }catch (IOException e){
                e.printStackTrace();
            }finally{
                if(inchannel != null){
                    try {
                        inchannel.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
    
                if(outChannel != null){
                    try {
                        outChannel.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
    
                if(fis != null){
                    try {
                        fis.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
    
                if(fos != null){
                    try {
                        fos.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    • 使用直接缓冲区完成文件的复制(内存映射文件)
    public class FileCopy2 {
    
        public static void main(String[] args) {
            test3();
        }
    
        private static void test3() {
    
            try {
                FileChannel inChannel = FileChannel.open(Paths.get("D:/1.jpg"), StandardOpenOption.READ);
                FileChannel outChannel = FileChannel.open(Paths.get("D:/3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ,
                StandardOpenOption.CREATE_NEW);
    
                //内存映射文件
                MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
                MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
    
                //直接对缓冲区进行数据的读写操作
                byte[] dst = new byte[inMappedBuf.limit()];
                inMappedBuf.get(dst);
                outMappedBuf.put(dst);
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    分散(Scatter)和聚集(Gather)

    • 分散读取(Scattering Reads): 将通道中的数据分散到多个缓冲区中
    • 聚集写入(Gathering Writes): 将多个缓冲区中的数据聚集到通道中
    public class FileCopy3 {
    
        public static void main(String[] args) {
            test4();
        }
    
        private static void test4() {
            try {
                RandomAccessFile raf1 = new RandomAccessFile("D:/3.txt","rw");
    
                //1.获取通道
                FileChannel channel1 = raf1.getChannel();
    
                //2.分配指定大小的缓冲区
                ByteBuffer buf1 = ByteBuffer.allocate(1);
                ByteBuffer buf2 = ByteBuffer.allocate(1024);
    
                //3.分散读取
                ByteBuffer[] bufs = {buf1,buf2};
                channel1.read(bufs);
    
                for (ByteBuffer byteBuffer : bufs) {
                    byteBuffer.flip();
                }
    
                //聚集写入
                RandomAccessFile raf2 = new RandomAccessFile("D:/7.txt","rw");
                FileChannel channel2 = raf2.getChannel();
                channel2.write(bufs);
    
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
    

    Selector

    • 是SelectableChannel的多路复用器,用于监控SelectableChannel的状态.它是实现NIO非阻塞的关键。
    • fileChannel不能切换成非阻塞式模式。
    Snipaste_2021-03-17_16-25-30.png

    阻塞式网络IO实现

    • 客户端
    public class FileTest {
    
        public static void main(String[] args) {
            client();
        }
    
        //客户端
        private static void client() {
    
            try {
                //1.获取通道
                SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("9.236.37.131", 9898));
    
                FileChannel inChannel = FileChannel.open(Paths.get("D:/1.jpg"), StandardOpenOption.READ);
    
                //2.分配指定大小的缓冲区
                ByteBuffer buf = ByteBuffer.allocate(1024);
    
                //3.读取本地文件,并发送到服务端
                while (inChannel.read(buf) != -1){
                    buf.flip();
                    sChannel.write(buf);
                    buf.clear();
                }
                //单向关闭自己的输出流,并未关闭连接,不写这个服务器端并不知道传输的图片结束了。一直卡在ssChannel.accept()
                sChannel.shutdownOutput();
    
                //接收服务器的反馈
                int len = 0;
                while ((len = sChannel.read(buf))!=-1){
                    buf.flip();
                    System.out.println(new String(buf.array(),0,len));
                    buf.clear();
                }
    
                //4.关闭通道
                inChannel.close();
                sChannel.close();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }
    
    • 服务端
    public class FileTest2 {
    
        public static void main(String[] args) {
            server();
        }
    
        //服务端
        private static void server() {
            try {
                //1.获取通道
                ServerSocketChannel ssChannel = ServerSocketChannel.open();
    
                FileChannel outChannel = FileChannel.open(Paths.get("D:/10.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
    
                //2.绑定链接
                ssChannel.bind(new InetSocketAddress(9898));
    
                //3.获取客户端连接的通道
                SocketChannel sChannel = ssChannel.accept();
    
                //4.分配指定大小的缓冲区
                ByteBuffer buf = ByteBuffer.allocate(1024);
    
                //5.接收客户端的数据,并保存到本地
                while (sChannel.read(buf)!= -1){
                    buf.flip();
                    outChannel.write(buf);
                    buf.clear();
                }
    
                //发送反馈给客户端
                buf.put("服务器接收数据成功".getBytes());
                buf.flip();
                sChannel.write(buf);
                //6.关闭通道
                sChannel.close();
                outChannel.close();
                ssChannel.close();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    非阻塞式网络IO实现

    • 客户端
    public class NonBlockClient {
    
        public static void main(String[] args) {
            client();
        }
    
        private static void client() {
    
            try {
                //1.获取通道
                SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("9.236.37.131", 9898));
    
                //2.切换非阻塞模式
                sChannel.configureBlocking(false);
    
                //3.分配指定大小的缓冲区
                ByteBuffer buf = ByteBuffer.allocate(1024);
    
                //4.发送数据给服务器
                Scanner scan = new Scanner(System.in);
    
                while (scan.hasNext()){
                    String str = scan.next();
                    buf.put((new Date().toString() + "\n"+str).getBytes());
                    buf.flip();
                    sChannel.write(buf);
                    buf.clear();
                }
    
                //5.关闭通道
                sChannel.close();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 服务端
    public class NonBlockServer {
    
        public static void main(String[] args) {
            server();
        }
    
        private static void server() {
    
            try {
                //1.获取通道
                ServerSocketChannel ssChannel = ServerSocketChannel.open();
    
                //2.切换非阻塞模式
                ssChannel.configureBlocking(false);
    
                //3.绑定链接
                ssChannel.bind(new InetSocketAddress(9898));
    
                //4.获取选择器
                Selector selector = Selector.open();
    
                //5.将通道注册到选择器上,并且指定“监听接收事件”
                //SelectionKey-----OP_ACCEPT,OP_CONNECT,OP_READ,OP_WRITE
                ssChannel.register(selector, SelectionKey.OP_ACCEPT);
    
                //6.轮询式的获取选择器上已经“准备就绪”的事件
                while (selector.select()>0){
    
                    //7.获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
                    Iterator<SelectionKey> it = selector.selectedKeys().iterator();
    
                    while (it.hasNext()){
                        //8. 获取准备“就绪”的事件
                        SelectionKey sk = it.next();
    
                        //9.判断具体是什么事件准备就绪
                        if(sk.isAcceptable()){
                            //10. 若“接收就绪”,获取客户端连接
                            SocketChannel sChannel = ssChannel.accept();
    
                            //11.切换非阻塞模式
                            sChannel.configureBlocking(false);
    
                            //12.将该通道注册到选择器上
                            sChannel.register(selector,SelectionKey.OP_READ);
                        }else if(sk.isReadable()){
                            //13. 获取当前选择器上“读就绪”状态的通道
                            SocketChannel sChannel = (SocketChannel)sk.channel();
    
                            //14. 读取数据
                            ByteBuffer buf = ByteBuffer.allocate(1024);
    
                            int len = 0;
                            while ((len = sChannel.read(buf))>0){
                                buf.flip();
                                System.out.println(new String(buf.array(),0,len));
                                buf.clear();
                            }
                        }
    
                        //15.取消选择键SelectionKey
                        it.remove();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • DatagramChannel --UDP用法类似
    • Pipe


      Snipaste_2021-03-17_16-54-55.png

    相关文章

      网友评论

          本文标题:NIO

          本文链接:https://www.haomeiwen.com/subject/eppkcltx.html