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


总结


原文地址:https://www.cnblogs.com/dahuandan/p/7076891.html