Java中IO详解

IO是构建程序的一个非常重要的一环。一个好的IO能够大大的提高程序的运行效率。IO分为BIO、NIO、AIO三种。接下来我们挨个详细讲解。

IO详解

1、BIO

BIO的全名是Blocking IO,就是阻塞式IO。BIO是Java中最古老的IO工具,Java出来的时候就有了。
在这里插入图片描述
服务器对应的是一个ServerSocket,它可以监听客户端发来的连接请求,然后为客户端创建一个Socket,客户端本身也有一个Socket,客户端和服务器就利用这一对Socket来传递数据,读取和写入是通过数据流实现的。

由于利用Socket来接收数据是会阻塞的,因为不是所有时间都会有数据传来的,没有数据传来的时候,socket会阻塞,这时服务器端对于一个客户端请求会分配一个线程Thread,然后在线程中阻塞的获取客户端发来的数据。这样的话有三个问题:
1、线程的创建和销毁是一个重量级操作。除了为线程分配内存、初始化等重量级操作外,还牵扯到用户态切换到内核态,需要安全检测以及用户栈运行状态信息的保存等操作,比较消耗资源。
2、线程本身比较占内存,线程对应的有虚拟机栈、本地方法栈等信息,如果系统中存在成千上万的线程,会极大的占用JVM的内存。
3、线程之间的切换的成本高,需要保存线程的运行状态信息,同时还会导致用户态切换到内核态。

代码:

public class EchoServer {
    public static void main(String[] args) throws IOException {
        //客户端ServerSocket用于接收客户端的申请
        ServerSocket serverSocket = new ServerSocket(8080);
        while (true) {
            //阻塞的接受客户端连接
            Socket socket = serverSocket.accept();

            //为每一个客户端新建thread来获取数据
            new Thread(()->{
                try {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    String msg;
                    //阻塞的获取数据,客户端没有发送数据就阻塞
                    while ((msg = reader.readLine()) != null) {
                        System.out.println("receive msg: " + msg);  
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

2、NIO
NIO的全称是New IO,是jdk1.4更新中引入的。NIO利用多路复用技术,改用单线程来处理多任务,改善了BIO的多线程浪费资源的问题。

NIO也是阻塞的。

在这里插入图片描述
NIO有三大核心:
1、Selector
Selector是事件分发器,客户端发送的写数据、读数据、连接等操作都是发送到Selector上的。Seletor调用select()方法来获取对应的所有客户端的操作,如果不存在就阻塞。
2、Channel
Channel分为ServerSocketChannel、SocketChannel。ServerSocketChannel是针对连接请求的,用于接收客户端的连接请求,然后为客户端初始化对应的SocketChannel。客户端本身也拥有一个SocketChannel,客户端和服务器就利用这一对SocketChannel来传递信息。
ServerSocketChannel和SocketChannel都需要注册到Selector中,这样Selector才会将其事件读取过来。
ServerSocketChannel注册的是ACCEPT类型的事件,而SocketChannel注册的是READABLE的事件。

3、Buffer
当客户端和服务器发送和读取数据的时候,将数据保存到哪呢?和BIO不同的是,NIO采用的是Buffer,缓冲池,将读取的数据和写入的数据存储在Buffer中。

Tip
1、一个Selector可以同时注册多个ServerSocketChannel。

代码

public class EchoServer {
    public static void main(String[] args) throws IOException {
        //事件分发器
        Selector selector = Selector.open();
        //服务器Channel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(8080));
        //是否阻塞的进行read()、write()
        serverSocketChannel.configureBlocking(false);
        // 将ServerSocketChannel利用accept事件绑定到selector上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 阻塞的获取客户端发来的事件
            selector.select();
            //获取事件set,SelectionKey对应的就是客户端发来的事件信息
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // 遍历selectKeys
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 如果是客户端连接事件
                if (selectionKey.isAcceptable()) {
                    //客户端想要连接的ServerSocketChannel
                    ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                    //为客户端创建SocketChannel
                    SocketChannel socketChannel = ssc.accept();
                    System.out.println("accept new conn: " + socketChannel.getRemoteAddress());
                    //非阻塞的read()和write()
                    socketChannel.configureBlocking(false);
                    //将SocketChannel注册到selector上,类型为read
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    //如果是客户端发来数据
                } else if (selectionKey.isReadable()) {
                    // 获取对应的SocketChannel
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    // 将数据读入到buffer中
                    int length = socketChannel.read(buffer);
                    if (length > 0) {
                        buffer.flip();
                        byte[] bytes = new byte[buffer.remaining()];
                        // 将数据读入到byte数组中
                        buffer.get(bytes);

                        String content = new String(bytes, "UTF-8").replace("\r\n", "");
                        if (content.equalsIgnoreCase("quit")) {
                            selectionKey.cancel();
                            socketChannel.close();
                        } else {
                            System.out.println("receive msg: " + content);
                        }
                    }
                }
                //将事件从set中移除,因为已经处理过了
                iterator.remove();
            }
        }
    }
}

NIO通过事件分发机制,达到了单线程就可以处理多任务的效果,节省了资源。但是NIO也有缺点:
1、如果并发量大的话,响应时间会变慢,因为是单线程,需要一个一个遍历。
2、如果有一个客户端的处理过程较慢,会影响其他客户端的操作,因为是串行处理的。

3、AIO
AIO的全称是Asynchronous IO,是jdk1.7更新引入的。AIO是异步IO,采用回调函数来异步调用,调用完IO后线程会继续执行。当IO完成后会自动调用回调函数。

由于linux系统底层不支持AIO,所以AIO被用的很少,不在仔细介绍了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值