Java中的流与IO

  • J3 - 白起
  • 技术(I/O流)

最近在看 Netty 相关的内容,以后就会写一些和 Netty 相关技术的文章。

而 Netty 作为业界最流行的 NIO 框架之一,在开始之前就自然要全面的介绍一下 BIONIO 以及 AIO 相关的内容了。

所以在开始 Netty 之前,我就来介绍介绍 I/O 的基本体系,以此来向你们构建出 Netty 的魅力。

一、流是什么

百度概念:

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

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

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

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

理解:

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

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

在这里插入图片描述

对于流的操作 Java 提供了非常多的 API 操作,包位置:java.iojava.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 模式

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

名词解释例子
同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪苦逼程单身程序员A,为了能找到相亲对象天天上班下班搜罗各大单身小姐姐联系方式,这种亲自出马忽略工作的就是同步。
异步异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知)聪明灵活单身程序员B,为了能找到各种相亲小姐姐的联系方式,直接联系了某大型婚介公司,交由婚介公司获取小姐姐联系方式,这种委托形式而不耽误正常工作的就是异步。
阻塞所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止程序员A,好不容易找到几个身材条件都不错的小姐姐,但是就是一直约不上人家,只能等人家空闲时间,这种等待就是阻塞
非阻塞非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待程序员B,因为是大型婚介公司,找得到联系方式,肯定也是约会相亲一条龙服务,所以就顺利的和小姐姐见面谈人生谈理想谈…,这种直接约的就是非阻塞

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

在这里插入图片描述

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

案例代码,见第一小节。

三、NIO

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

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

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

NIO模型图如下:

在这里插入图片描述

案例:

@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 讲解了,关注我,咱们下期见。

查阅或扩展资料:


  • 由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。

  • 如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。

  • 感谢您的阅读,十分欢迎并感谢您的关注。

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

优快云:J3 - 白起

掘金:J3-白起

知乎:J3-白起

这是一个技术一般,但热衷于分享;经验尚浅,但脸皮够厚;明明年轻有颜值,但非要靠才华吃饭的程序员。

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

在IT领域,尤其是地理信息系统(GIS)中,坐标转换是一项关键技术。本文将深入探讨百度坐标系、火星坐标系和WGS84坐标系之间的相互转换,并介绍如何使用相关工具进行批量转换。 首先,我们需要了解这三种坐标系的基本概念。WGS84坐标系,即“World Geodetic System 1984”,是一种全球通用的地球坐标系统,广泛应用于GPS定位和地图服务。它以地球椭球模为基础,以地球质心为原点,是国际航空和航海的主要参考坐标系。百度坐标系(BD-09)是百度地图使用的坐标系。为了保护隐私和安全,百度对WGS84坐标进行了偏移处理,导致其WGS84坐标存在差异。火星坐标系(GCJ-02)是中国国家测绘局采用的坐标系,同样对WGS84坐标进行了加密处理,以防止未经授权的精确位置获取。 坐标转换的目的是确保不同坐标系下的地理位置据能够准确对应。在GIS应用中,通常通过特定的算法实现转换,如双线性内插法或四参转换法。一些“坐标转换小工具”可以批量转换百度坐标、火星坐标WGS84坐标。这些工具可能包含样本文件(如org_xy_格式参考.csv),用于提供原始坐标据,其中包含需要转换的经纬度信息。此外,工具通常会附带使用指南(如重要说明用前必读.txt和readme.txt),说明输入据格式、转换步骤及可能的精度问题等。x86和x64目录则可能包含适用于32位和64位操作系统的软件或库文件。 在使用这些工具时,用户需要注意以下几点:确保输入的坐标据准确无误,包括经纬度顺序和浮点精度;按照工具要求正确组织据,遵循读写规则;注意转换精度,不同的转换方法可能会产生微小误差;在批量转换时,检查每个坐标是否成功转换,避免个别错误据影响整体结果。 坐标转换是GIS领域的基础操作,对于地图服务、导航系统和地理据分析等至关重要。理解不同坐标系的特点和转换方法,有助于我们更好地处
智能网联汽车的安全员级考试涉及多个方面的专业知识,包括但不限于自动驾驶技术原理、车辆传感器融合、网络安全防护以及法律法规等内容。以下是针对该主题的一些核心知识点解析: ### 关于智能网联车安全员级考试的核心内容 #### 1. 自动驾驶分级标准 国际自动机工程师学会(SAE International)定义了六个级别的自动驾驶等级,从L0到L5[^1]。其中,L3及以上级别需要安全员具备更的应急处理能力。 #### 2. 车辆感知系统的组成功能 智能网联车通常配备多种传感器,如激光雷达、毫米波雷达、摄像头和超声波传感器等。这些设备协同工作以实现环境感知、障碍物检测等功能[^2]。 #### 3. 据通信网络安全 智能网联车依赖V2X(Vehicle-to-Everything)技术进行据交换,在此过程中需防范潜在的网络攻击风险,例如中间人攻击或恶意软件入侵[^3]。 #### 4. 法律法规要求 不同国家和地区对于无人驾驶测试及运营有着严格的规定,考生应熟悉当地交通法典中有关自动化驾驶部分的具体条款[^4]。 ```python # 示例代码:模拟简单决策逻辑 def decide_action(sensor_data): if sensor_data['obstacle'] and not sensor_data['emergency']: return 'slow_down' elif sensor_data['pedestrian_crossing']: return 'stop_and_yield' else: return 'continue_driving' example_input = {'obstacle': True, 'emergency': False, 'pedestrian_crossing': False} action = decide_action(example_input) print(f"Action to take: {action}") ``` 需要注意的是,“橙点同学”作为特定平台上的学习资源名称,并不提供官方认证的标准答案集;建议通过正规渠道获取教材并参加培训课程来准备此类资格认证考试。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J3code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值