zoukankan      html  css  js  c++  java
  • IO

    IO是一个复杂的东西,它包含了文件、网络、缓冲、数据等很多需要解决的功能。
    因此java有很多类来解决这个难题,发展到现在,IO类库有过几次重大改变:JDK1.4添加了NIO,JDK1.7添加了AIO。

    IO的五种模型

     在了解IO之前我们先要清楚IO的5个模型,可以让我们知道IO的区别,这也是IO发展的根本原因。

    阻塞IO模型


    最传统的一种IO模型,即在读写数据过程中会发生阻塞现象:

    当用户发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪,用户就会让出cpu等待数据就绪,此时用户进程就处于阻塞状态;

    当数据就绪之后,内核会将数据拷贝到用户内存,并返回结果给用户,用户进程才解除阻塞状态;

    阻塞时会挂起用户线程,如果同时有多个用户进行IO操作,则需要创建多个线程来处理,造成了性能瓶颈。

    非阻塞IO模型


    当用户发起一个IO操作后,会主动地通过轮询方式请求内核的数据是否就绪;

    这个过程非阻塞IO并不会让出CPU执行权,所以不会阻塞用户进程,而是马上就得到了一个结果;

    如果结果是一个error时,它就知道数据还没有准备好,于是用户再次发送read操作;

    一旦内核中的数据准备好了就把数据拷贝给用户;

    非阻塞IO如果有大量用户线程进行io操作,它们会一直占用CPU请求内核数据是否就绪,导致cpu占用率很高;

    另外任务完成的响应延迟增大了,因为内核数据可能在两次轮询之间完成就绪,此时需要等待用户进程再次轮询。

    多路复用IO模型


    内核会有一个线程不断去轮询多个socket的状态,只有当socket真正有IO事件时,才真正调用实际的IO操作;

    与非阻塞IO的区别是轮询是通过内核自身的进程来执行,而非阻塞IO是通过用户进程主动发起轮询,所以效率上要比非阻塞高;

    与传统的多线程+ 阻塞IO相比,多路复用IO只需要一个线程就可以管理多个socket,系统不需要建立、维护新的io线程,所以它大大减少了资源占用; 

    不过要注意的是,多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应;

    因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询;

    所以多路复用IO模型的不适于处理任务较重的socket(多线程+ 阻塞IO,非阻塞IO适用),而是在于能处理大量、任务轻的socket。

    信号驱动IO模型


    当用户进程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户进程会继续执行;

    当内核数据就绪时会发送一个信号给用户进程,用户进程接收到就绪信号之后,便在信号函数中调用IO操作。 

    异步IO模型


    当用户发起IO操作之后,立刻就可以开始去做其它的事;

    而另一方面,当内核接收到一个IO请求后会立刻返回,说明IO请求已经成功发起了,因此不会对用户进程产生任何block;

    然后,内核会等待数据就绪,再进行IO操作,当这一切都完成之后,内核会给用户进程发送一个信号,告诉它read操作完成了;

    异步IO不用像信号驱动模型那样,在接收到就绪信号后再次调用IO操作,而是当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。

    总结


    前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,都是顺序执行的,需要等待内核的数据就绪;

    BIO

    概念


    传统面向流的老IO属于阻塞IO模型,被称为BIO;

    它分为两大类别:字节流与字符流;

    它们的体系是通过装饰器模式来设计的,即我们在使用IO某些功能的时候需要创建多个类,装饰器的基类是FilterInputStream、FilterOutputStream,例如需要使用高效字节流:

    //BufferedInputStream继承FilterInputStream
    BufferedInputStream bi = new BufferedInputStream(new FileInputStream("D:\java\aaa.txt"));

    字节流可以转换成字符流,这是通过适配器模式实现的,适配器类有InputStreamReader、OutputStreamWriter。

    Demo(多用户IO)


    流程图:

        

    Server:

    package io.bio;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * BIO服务端
     * @author DaHuaiDan
     *
     */
    public class Server {
        
        
        public static void main(String[] args) throws IOException {
            
            ServerSocket server =null;
            
            
            try {
                
                //创建ServerSocket,监听12345端口
                server = new ServerSocket(12345);
                Socket socket;
    
    
                //处理Socket
                while (true) {
                    
                    socket = server.accept();//监听socket
                    
                    
                    /**
                     * BIO是阻塞的,所以 一个线程处理一个socket
                     */
                    new Thread(new SocketHandler(socket)).start();
                }
                
                
            } finally {
                
                if (server != null)
                    server.close();
            }
        }
    }
    View Code

    SocketHandler:

    package io.bio;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.Socket;
    
    /**
     * 处理Socket线程类
     * 
     * @author DaHuaiDan
     * 
     */
    public class SocketHandler implements Runnable {
    
        private Socket socket;
    
        
        public SocketHandler(Socket socket) {
    
            this.socket = socket;
        }
    
        
        @Override
        public void run() {
            BufferedReader br =null;
            
            try {
                
                //获取客户端IO
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String message;
                
                while (true) {
                    
                    if ((message = br.readLine()) != null)
                        System.out.println("server:  " + message);
                }
    
                
            } catch (Exception e) {
    
                e.printStackTrace();
    
            } finally {
    
                if (br != null)
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
            }
        }
    }
    View Code

    Client:

    package io.bio;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.net.Socket;
    
    /**
     * BIO客戶端
     * @author DaHuanDan
     *
     */
    public class Client {
    
        
        public static void main(String[] args) throws IOException {
    
            int i =0;
            while(true){
                send("massage" + i++);
            }
        }
        
        
        /**
         * 发送信息到服务端
         * @param massage
         * @throws IOException
         */
        private static void send(String massage) throws IOException{
            
            Socket socket = null;
            PrintWriter pw = null;
    
            
            try{
    
                socket = new Socket("127.0.0.1",12345);
                pw = new PrintWriter(socket.getOutputStream(),true);
                pw.print(massage);
                
                
            }catch(Exception e){
    
                e.printStackTrace();
    
            }finally{
    
                if(pw != null)
                    pw.close();
    
                if(socket != null)
                    socket.close();
            }
        }
    }
    View Code

    总结


     BIO属于阻塞IO模型,不适合多用户并发操作,但是它不同于非阻塞IO、多路复用IO需要轮询内核,所以对单个、任务较重的IO情况下,性能会好些。

    NIO

    概念


    引用Thinging in Java的一段话:JDK1.4引入了NIO,其目的是提高速度,实际上旧IO已经使用NIO重新实现过,以便充分利用这些好处;

    面向缓冲区的NIO属于多路复用IO模型,除了性能的提高,基于缓冲区操作让NIO可以在传输过程灵活的操作数据;

    NIO提供了SocketChannel和ServerSocketChannel对应传统BIO模型中的Socket和ServerSocket,新增的两种通道都支持阻塞和非阻塞两种模式,适用于效率、并发等应用场景;

    掌握NIO,首先我们需要了解它的三要素。

    NIO的三要素


    buffer相当于卡车,channel是煤矿通道,Selector监视所有通道,当selector监视到某通道有卡车从煤矿里把煤炭运输出来,我们再从卡车上获得煤炭。

    Buffer 缓冲区:

    读写数据都是在buffer缓冲区中处理的,缓冲区实际上是一个数组,提供了对数据访问以及维护Channel通道位置等信息;

    具体的缓存区有:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer,还有MappedByteBuffer, HeapByteBuffer等,用来支持用户操作的不同场景。

    Channel 通道:

    Channel用于连接数据源、目的地,建立起两段的交互通道,用于Buffer在通道中运输数据, 

    Channel主要分两大类:SelectableChannel(用户网络读写)、FileChannel(用于文件操作)。

    Selector 多路复用器:

    Selector用于监视、处理多个Channel通道,原理是通过内核自身的一个单线程进行轮询;

    Demo(多用户IO)


    流程图:

        

     

    Server:

    package io.nio;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.util.Iterator;
    
    
    /**
     * NIO服务端
     * @author DaHuaiDan
     *
     */
    public class Server {
        
        
        public static void main(String[] args) {
             selector();
        }
    
        
        /**
         * selector用法示例
         */
        public static void selector() {
            
            Selector selector = null;
            ServerSocketChannel ssc = null;
            
            
            try {
                
                //打开复用器
                selector = Selector.open();
                
                //打开服务端的监听通道
                ssc = ServerSocketChannel.open();
                
                //复用器监听的端口为12345
                ssc.socket().bind(new InetSocketAddress(12345));
                
                /*
                 * true:通道将被置于阻塞模式;false:通道将被置于非阻塞模式;
                 * 与Selector一起使用时,Channel必须处于非阻塞模式下;
                 * FileChannel不能切换到非阻塞模式.
                 */
                ssc.configureBlocking(false);
                
                /*
                 * 将Channel注册到Selector上;
                 * SelectionKey有四个参数Connect、 Accept、 Read、Write,代表Selector可以监听四种不同类型的事件
                 */
                ssc.register(selector, SelectionKey.OP_ACCEPT);
    
                
                while (true) {
                    
                    
                    /**
                     * 设置select轮询时间为1s:
                     * NIO是多路复用IO,通过selector轮询客户端socket即可,不需创建多个线程来处理多个socket:
                     */
                    if (selector.select(1000) == 0) {
                        
                        System.out.println("服务端select无事件到达");
                        continue;
                    }
                    
                    //获取SelectionKey事件集
                    Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                    
                    //处理SelectionKey事件集
                    SelectionKeyHandler.getSelectionKeyHandler(iter).handler();
                }
    
                
            } catch (IOException e) {
                
                e.printStackTrace();
                
                
            } finally {
                
                
                try {
                    
                    
                    if (selector != null) 
                        selector.close();
                    
                    if (ssc != null) 
                        ssc.close();
                    
                    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    View Code

    SelectionKeyHandler:

    package io.nio;
    
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    
    /**
     * 处理electionKey事件类
     * @author DaHuaiDan
     *
     */
    public class SelectionKeyHandler {
        
        
        
        private static SelectionKeyHandler selectionKeyHandler = new SelectionKeyHandler();
        
        private static Iterator<SelectionKey> ite;
        
        
        private SelectionKeyHandler() {
        }
        
        
        /**
         * 获取SelectionKeyHandler
         * @param it
         * @return
         */
        public static SelectionKeyHandler getSelectionKeyHandler(Iterator<SelectionKey> it) {
            
            ite = it;
            
            return selectionKeyHandler;
        }
    
    
        /**
         * 处理SelectionKey事件集
         * @throws IOException 
         */
        public void handler() throws IOException {
            
            while (ite.hasNext()) {
                
                
                SelectionKey key = ite.next();
                
                
                if (key.isAcceptable()) 
                    handleAccept(key);
                
                
                else if (key.isReadable()) 
                    handleRead(key);
                
                
                else if (key.isWritable() && key.isValid()) 
                    handleWrite(key);
                
                
                else if (key.isConnectable()) 
                    System.out.println("isConnectable = true");
                
                ite.remove();
            }
        }
        
        
    
        /**
         * 处理接入事件的函数
         * @param key
         * @throws IOException
         */
        public void handleAccept(SelectionKey key) throws IOException {
            
            //获取复用器的监听通道
            ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
            
            //通过监听获取事件发生的socket通道
            SocketChannel sc = ssChannel.accept();
            
            //设置为非阻塞的 
            sc.configureBlocking(false);
            
            //向Selector注册该SocketChannel,事件为read,指定该SocketChannel使用的Buffer
            sc.register(key.selector(), SelectionKey.OP_READ);
        }
        
        
        /**
         * 处理IO read函数
         * @param key
         * @throws IOException
         */
        public void handleRead(SelectionKey key) throws IOException {
            
            //获取复用器的监听通道
            SocketChannel sc = (SocketChannel) key.channel();
            
            //创建缓冲区Buffer
            ByteBuffer buf = ByteBuffer.allocate(1024);
            
            
            //读取数据
            while(sc.read(buf) > 0){
                
                //将读取的起始位置position重置为0
                buf.flip();
                
                System.out.println("Server:  " + new String(buf.array(), 0, buf.limit()));
                
                //清空缓冲区
                buf.clear();
            }
            
            
            sc.close();
        }
    
        
        /**
         * 处理IO writer函数
         * @param key
         * @throws IOException
         */
        public void handleWrite(SelectionKey key) throws IOException {
            
            ByteBuffer buf = (ByteBuffer) key.attachment();
            SocketChannel sc = (SocketChannel) key.channel();
            
            
            //发送数据到SocketChannel
            while (buf.hasRemaining()) 
                sc.write(buf);
            
            buf.compact();
        }
    }
    View Code

    Client:

    package io.nio;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.SocketChannel;
    
    
    /**
     * NIO客户端
     * @author DaHuaiDan
     *
     */
    public class Client {
        
        public static void main(String[] args) throws IOException, InterruptedException {
    
            int i =0;
            while(true){
                send("message" + i++);
                Thread.sleep(100);//防止本地socket吃不消
            }
        }
        
        
        /**
         * NIO发送数据
         * @param massage
         * @throws IOException
         */
        private static void send(String massage) throws IOException {
            
            Selector selector = null;
            SocketChannel sc = null;
            
            
            try {
                
                //打开复用器
                selector = Selector.open();
                
                //打开客户端监听通道
                sc = SocketChannel.open();
                
                /*
                 * true:通道将被置于阻塞模式;false:通道将被置于非阻塞模式;
                 * 与Selector一起使用时,Channel必须处于非阻塞模式下;
                 * FileChannel不能切换到非阻塞模式.
                 */
                sc.configureBlocking(false);
                
                //将Channel注册到Selector上;
                sc.register(selector, SelectionKey.OP_CONNECT);
                
                //连接服务器
                sc.connect(new InetSocketAddress("127.0.0.1",12345));
                
                
                if (sc.isConnectionPending()) {
                    
                    //完成连接动作
                    sc.finishConnect();
                    
                    //发送数据到SocketChannel
                    sc.write(ByteBuffer.wrap(massage.getBytes()));
                }
                
                
            } catch (IOException e) {
                
                e.printStackTrace();
                
                
            } finally {
                
                
                try {
                    
                    
                    if (selector != null) 
                        selector.close();
                    
                    if (sc != null) 
                        sc.close();
                    
                    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    View Code

    总结


     从流程图可以看出来,NIO的核心就是Selector:通过单个线程实现,轮询socket客户端;

    之前的多路复用小节中也介绍了该IO模型的优缺点,适用于IO操作小,连接多的特点。

    AIO

    概念


    JDK1.7加入了AIO,主要为了实现异步通道,属于异步IO模型;

    AIO详细的介绍日后再补充。

    Demo


    总结


  • 相关阅读:
    Android 4.0锁屏机制类之间的调用关系
    给盲目兴奋的程序员们的建议
    Hadoop相对于RDBMS、HPC、志愿计算的比较
    vmware7.1.14的vmware tools不支持opensuse12的解决过程
    集群的分类
    Suse linux和OpenSuse的区别和联系
    Apache Hadoop项目
    linux下安装JDK
    sudo的详细用法
    ubuntu和debian环境下vmware虚拟机共享目录无法挂载的问题解决办法
  • 原文地址:https://www.cnblogs.com/dahuandan/p/7076891.html
Copyright © 2011-2022 走看看