• (五)非阻塞式编程NIO


    1.NIO概述

    Channel与Stream的不同:

    • Stream是具有方向性的,有输入流 or 输出流;Channel是双向的,既可以读又可以写。
    • Stream的读和写都是阻塞式的;但是Channel有两种模式,既可以阻塞式读写,又可以非阻塞式读写

    • 非阻塞的意思是,例如想从某个Channel中读取数据,但是调用读取的瞬间Channel上还没有可以被读的数据,由于非阻塞,那么调用马上就返回了。所以,为了读到某个Channel上的数据,需要不停的询问它,因此我们使用Selector。

    Note:如果一个任务单线程就可以执行,那么往往比使用多线程效率要高,NIO就是一个例子。多线程不一定更有效率,因为:

    • 如果需要处理线程的数量多于CPU处理器的数量,会出现“上下文交换”。CPU的每一次切换都需要先保存当前线程的状态,之后重新执行该线程时,要加载原先线程的状态。在各个线程发生的交换过程,需要消耗系统资源;
    • 每创建一个线程,系统都要为其分配一定的系统资源。

    2.Buffer简析

     

    3.Channel简析

     

    4.使用BIO和NIO实战:本地文件拷贝

    interface FileCopyRunner {
        void copyFile(File source, File target);
    }
    
    public class FileCopyDemo {
        private static final int ROUNDS = 3; // 每种方法都运行3次来评估性能
    
        // 执行不同方法的函数,并评估性能
        private static void benchmark(FileCopyRunner test, File source, File target) {
            long elapsed = 0L; // 总时间
            for (int i = 0; i < ROUNDS; i++) {
                long startTime = System.currentTimeMillis();
                test.copyFile(source, target);
                elapsed += System.currentTimeMillis() - startTime;
                if (i != ROUNDS - 1) {
                    target.delete(); // 每次拷贝后再删除
                }
            }
            System.out.println(test + ":" + elapsed / ROUNDS);
        }
    
        // 关闭各种实现了Closeable接口的资源
        private static void colse(Closeable closeable) {
            if (closeable != null) {
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
    
            // 一、不使用缓冲的流的拷贝(一个字节一个字节地拷贝)
            FileCopyRunner noBufferStreamCopy = new FileCopyRunner() {
                @Override
                public void copyFile(File source, File target) {
                    InputStream fin = null;
                    OutputStream fout = null;
                    try {
                        fin = new FileInputStream(source);
                        fout = new FileOutputStream(target);
    
                        int result;
                        try {
                            // 如果读到数据的结尾时,会返回-1
                            while ((result = fin.read()) != -1) {
                                // 每读到一个字节,就把字节写到文件输出流中
                                fout.write(result);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } finally {
                        colse(fin);
                        colse(fout);
                    }
                }
    
                @Override
                public String toString() {
                    return "noBufferStreamCopy";
                }
            };
    
            // 二、使用缓冲区的流的拷贝
            FileCopyRunner bufferedStreamCopy = new FileCopyRunner() {
                @Override
                public void copyFile(File source, File target) {
                    InputStream fin = null;
                    OutputStream fout = null;
                    try {
                        fin = new BufferedInputStream(new FileInputStream(source));
                        fout = new BufferedOutputStream(new FileOutputStream(target));
    
                        // 缓冲区大小可以自己定义,例如定义为1024字节.
                        byte[] buffer = new byte[1024];
    
                        int result;
                        while ((result = fin.read(buffer)) != -1) {
                            // 一次可读1024个字节,而不是1个字节了. result返回当前从buffer中读取的字节数
                            fout.write(buffer,0,result);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        colse(fin);
                        colse(fout);
                    }
                }
    
                @Override
                public String toString() {
                    return "bufferedStreamCopy";
                }
            };
    
            // 三、channel与buffer做数据交换 nio
            FileCopyRunner nioBufferCopy = new FileCopyRunner() {
                @Override
                public void copyFile(File source, File target) {
                    FileChannel fin = null;
                    FileChannel fout = null;
    
                    // 由文件得到对应的文件通道
                    try {
                        fin = new FileInputStream(source).getChannel();
                        fout = new FileOutputStream(target).getChannel();
    
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        // 把数据从通道中读入到缓冲区
                        while ((fin.read(buffer)) != -1) {
                            // 把buffer从写模式切换到读模式,内部通过调整position指针和limit指针来实现
                            buffer.flip();
                            // 加循环的作用:为了保证把buffer中所有的数据都读取到目标文件通道中
                            while (buffer.hasRemaining()) {
                                fout.write(buffer);
                            }
                            // 读模式调整为写模式,内部通过使position指针回到初始位置,limit回到最远端
                            buffer.clear();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        colse(fin);
                        colse(fout);
                    }
                }
    
                @Override
                public String toString() {
                    return "nioBufferCopy";
                }
            };
    
            // 四、通道与通道间传输数据 nio
            FileCopyRunner nioTransferCopy = new FileCopyRunner() {
                @Override
                public void copyFile(File source, File target) {
                    FileChannel fin = null;
                    FileChannel fout = null;
                    try {
                        fin = new FileInputStream(source).getChannel();
                        fout = new FileOutputStream(target).getChannel();
    
                        long transfered = 0L; // 目前为止已经拷贝了多少字节的数据
                        long size = fin.size();  // 要复制文件的大小
                        // transferTo()函数不能保证把原通道所有数据都传输到目标通道
                        while (transfered != size) {
                            transfered += fin.transferTo(0, size, fout); // 第二个参数是要传输多少数据
                        }
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }finally {
                        colse(fin);
                        colse(fout);
                    }
                }
    
                @Override
                public String toString() {
                    return "nioTransferCopy";
                }
            };
    
    
            File source = new File("F:\test\project.zip");
            File target1 = new File("F:\test\p1.zip");
            File target2 = new File("F:\test\p2.zip");
            File target3 = new File("F:\test\p3.zip");
            File target4 = new File("F:\test\p4.zip");
    
            benchmark(noBufferStreamCopy, source, target1);
            benchmark(bufferedStreamCopy, source, target2);
            benchmark(nioBufferCopy, source, target3);
            benchmark(nioTransferCopy, source, target4);
        }
    }

    5.实验结果

    • 以复制大小为2.75MB的文件为例,一个字节一个字节地拷贝实在是慢的可怕。。

    6.Selector简析

    • 作用:不停地“询问”通道什么时候处于可操作状态,即监听多个通道的状态。

     

    •  select()返回的数值表示目前有几个Channel处于可操作状态。
    • 目的是,跟Selector这一个对象互动,就可以得到Selector所监听的多个Channel对象状态的改变,由此实现进一步的业务逻辑。
    • SelectionKey的理解:每一个在Selector上注册的Channel,都相当于对应一个独特的“ID”,就是SelectionKey
  • 相关阅读:
    《JS权威指南学习总结--6.6属性getter和setter》
    《JS权威指南学习总结--6.5枚举属性》
    django的模板系统
    django额外参数的传递和url命名
    python在图片上画矩形
    当爬虫遇到js加密
    php基础-7
    php基础-6
    php基础-5
    php基础-4
  • 原文地址:https://www.cnblogs.com/HuangYJ/p/14454094.html
Copyright © 2020-2023  润新知