Java中的流与IO

简介: 最近在看 Netty 相关的内容,以后就会写一些和 Netty 相关技术的文章。而 Netty 作为业界最流行的 NIO 框架之一,在开始之前就自然要全面的介绍一下 BIO、NIO 以及 AIO 相关的内容了。所以在开始 Netty 之前,我就来介绍介绍 I/O 的基本体系,以此来向你们构建出 Netty 的魅力。


一、流是什么

百度概念:


流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。


总结就是:流就是传输,传输的内容就是字节


在 Java 中我们常说的字节流、字符流其实本质就是对流传输内容的不同而划分的两种操作。字节流操作单位是字节,字符流操作单位是一个个字符。


前面说过,流是有起点和终点的。而又因为起点和终点的各不相同,流又可分为:输入流、输出流。


理解:


内存 -> 硬盘 = 输出流(OutputStream、Writer)


硬盘 -> 内存 = 输入流(InputStream、Reader)


image.png


对于流的操作 Java 提供了非常多的 API 操作,包位置:java.io、java.nio。


因为本篇不是教大家如何使用 API 的,所以其中的使用方法就不过多的介绍了,但我岂是那种不负责任的男人😀,已经帮你们找好要复习 IO 流操作的基础教程了👉👉👉点这里。


但为了引出 BIO、NIO及 AIO 相关概念,小小案例还是要写一下的,如下:


@Slf4j
public class BioTest {
    /**
     * 流的形式操作文件
     */
    @Test
    public void streamTest() throws Exception {
        // 定义两个文件,in.txt 和 out.txt(提前在src目录下创建好两个文件)
        File inFile = new File("src/in.txt");
        File outFile = new File("src/out.txt");
        // 定义一个对 in.txt 操作的流对象
        InputStream inputStream = new FileInputStream(inFile);
        // 开始读取文件
        byte[] bytes = new byte[8];
        inputStream.read(bytes);
        System.out.println(new String(bytes));
        // 定义 out.txt 操作的流对象
        OutputStream outputStream = new FileOutputStream(outFile);
        // 开始写出文件
        outputStream.write(bytes);
        outputStream.write("\nout-write".getBytes(StandardCharsets.UTF_8));
    }
    /**
     * 阻塞形式操作网络编程
     */
    @Test
    public void blockTest() throws Exception {
        /*
        1、运行服务端发现,服务端在获取客户端连接及获取客户端数据时,都会堵塞
        */
    }
    @Test
    public void server() throws Exception {
        // 创建服务端对象
        ServerSocket serverSocket = new ServerSocket(9528);
        log.info("创建了一个服务端对象,{}", serverSocket);
        // 获取客户端链接的 soclet 对象
        Socket accept = serverSocket.accept();
        log.info("获取到了客户端连接对象,{}", accept);
        InputStream inputStream = accept.getInputStream();
        byte[] bytes = new byte[100];
        inputStream.read(bytes);
        log.info("客户端发来的内容:{}", new String(bytes));
    }
    @Test
    public void client() throws Exception {
        // 创建客户端,指定服务端ip及端口,进行连接
        Socket client = new Socket("localhost", 9528);
        log.info("客户端创建完毕,{}", client);
        // 开始向务端发送消息
        OutputStream outputStream = client.getOutputStream();
        outputStream.write("hello world!".getBytes(StandardCharsets.UTF_8));
    }
}


上面案例展示了两种效果,一流操作、二阻塞操作。


而对于流和阻塞正是 Java 传统 IO(BIO) 的一种拙劣表现,流在数据的运输上效率比不上带有通道的缓冲区;而阻塞更比不上非阻塞的 Selector 操作。


二、BIO

同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。


而且也是 Java 1.4 之前唯一的 IO 模式。


上面出现了两个名词:同步,阻塞,那么下面我先解释一下。


image.png


通过上面我写的代码案例和这张表格,相信大家对与同步及阻塞有了很清晰的认识了。下面我们一起看看 BIO 的模型图:


image.png


从图中可以看出,一个服务器会对应这多个客户端,每个客户端都对着不同的线程,这就导致了单线程环境下客户端 A 与服务端通信时,B、C 都需要进行等待阻塞,只有 A 通信完毕 B、C 客户端才能进行后续步骤。


案例代码,见第一小节。


三、NIO


同步非阻塞I/O模式,Java 1.4 之后开始支持,并提供了像 Channel , Selector,Buffer等抽象(后面会重点介绍这三大组件)。


我们都知道传统 BIO 是面向流的,而 NIO 意识到流的效率问题就提出了面向缓冲(块)的方式进行 IO 操作大大提升了传输效率。


而且 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO的非阻塞模式来开发。


NIO模型图如下:


image.png


案例:


@Slf4j
public class NioTest {
    @Test
    public void serverTest() throws Exception{
        //创建serverSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //得到Selector对象
        try (Selector selector = Selector.open()) {
            //把ServerSocketChannel注册到selector,事件为OP_ACCEPT
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            //如果返回的>0,表示已经获取到关注的事件
            while (selector.select() > 0) {
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    //获得到一个事件
                    SelectionKey next = iterator.next();
                    //如果是OP_ACCEPT,表示有新的客户端连接
                    if (next.isAcceptable()) {
                        //给该客户端生成一个SocketChannel
                        SocketChannel accept = serverSocketChannel.accept();
                        accept.configureBlocking(false);
                        //将当前的socketChannel注册到selector,关注事件为读事件,同时给socket Channel关联一个buffer
                        accept.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                        log.info("获取到一个客户端连接");
                        //如果是读事件
                    } else if (next.isReadable()) {
                        //通过key 反向获取到对应的channel
                        SocketChannel channel = (SocketChannel) next.channel();
                        //获取到该channel关联的buffer
                        ByteBuffer buffer = (ByteBuffer) next.attachment();
                        while (channel.read(buffer) != -1) {
                            buffer.flip();
                            log.info(new String(buffer.array(), 0, buffer.limit()));
                            buffer.clear();
                        }
                    }
                    // 这个很重要,在处理完事件之后,要移除该事件
                    iterator.remove();
                }
            }
        }
    }
    @Test
    public void clientTest() throws Exception{
        //得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        //设置为非阻塞
        socketChannel.configureBlocking(false);
        //提供服务器端的IP和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost", 9528);
        //连接服务器
        if (!socketChannel.connect(inetSocketAddress)) {
            while (!socketChannel.finishConnect()) {
                log.info("连接需要时间,客户端不会阻塞...你可以去干别的事情了");
            }
        }
        //连接成功,发送数据
        String str = "J3-白起";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        socketChannel.close();
        log.info("客户端退出");
    }
}


这段代码,可以体现服务端获取客户端连接时时不需要阻塞的,并且在读取客户端发来的数据时也是不需要阻塞有数据就读没有就往下执行,这也是 Netty 框架流行原因之一。


四、AIO


异步非阻塞I/O模式,在 Java 7 中引入了 NIO 的改进版 NIO 2。


异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。


对于 AIO 在网上资料还不是很多,并且应用还不是很广泛,所以就…


五、最后


本篇主要是让大家对 Java IO 的模型体系有个大致了解,知道有 BIO、NIO、AIO 这一回事和了解同步、阻塞的区别就行。


对于具体的应用,我是没有具体展开说的,因为 BIO 是基础我已经贴过教程地址了☝☝☝。而 NIO 是学 Netty 的前提我后面会对这部分持续的输出,至于 AIO 应用还不是非常广泛我也不会,所以可以先不用关注了解就行。


好了,介绍了这篇,那下篇就是 NIO 讲解了,关注我,咱们下期见。


目录
相关文章
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
402 0
|
11月前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
396 1
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
存储 Java Linux
操作系统层面视角下 Java IO 的演进路径及核心技术变革解析
本文从操作系统层面深入解析Java IO的演进历程,涵盖BIO、NIO、多路复用器及Netty等核心技术。分析各阶段IO模型的原理、优缺点及系统调用机制,探讨Java如何通过底层优化提升并发性能与数据处理效率,全面呈现IO技术的变革路径与发展趋势。
294 3
|
SQL Java 数据库连接
Java IO流(一):字节流与字符流基础
本文全面解析Java IO流,涵盖字节流、字符流及其使用场景,帮助开发者理解IO流分类与用途,掌握文件读写、编码转换、异常处理等核心技术,通过实战案例提升IO编程能力。
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
741 23
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
598 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
Java
java 中 IO 流
Java中的IO流是用于处理输入输出操作的机制,主要包括字节流和字符流两大类。字节流以8位字节为单位处理数据,如FileInputStream和FileOutputStream;字符流以16位Unicode字符为单位,如FileReader和FileWriter。这些流提供了读写文件、网络传输等基本功能。
407 10
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)