当前位置: 首页 > news >正文

IO系列(一) -一文带你读懂 java 中的IO流!

一、摘要

说到 IO,相信大家都不陌生,英文全称:Input/Output即输入/输出,通常指数据在内部存储器和外部存储器或其他周边设备之间的输入和输出。

比如我们常用的SD卡、U盘、移动硬盘等等存储文件的硬件设备,当我们将其插入电脑的 usb 硬件接口时,我们就可以从电脑中读取设备中的信息或者写入信息,这个过程就涉及到 I/O 的操作。

当然,涉及 I/O 的操作,也不仅仅局限于硬件设备的读写,还有网络数据的传输。比如,我们在电脑上用浏览器搜索互联网上的信息,这个信息的过程也涉及到 I/O 的操作。

无论是从磁盘中读写文件,还是在网络中传输数据,可以说 I/O 主要为处理人机交互、机与机交互中获取和交换信息提供的一套解决方案。

在 Java 的 IO 体系中,类将近有 80 个,位于java.io包下,初步看起来感觉非常复杂,但是经过一番梳理之后,你会发现还是有规律可循的。

传输数据的格式角度看,可以大致分为两组:

  • 基于字节操作的 I/O 接口:InputStream 和 OutputStream
  • 基于字符操作的 I/O 接口:Reader 和 Writer

传输数据的方式角度看,也可以大致分为两组:

  • 基于磁盘操作的 I/O 接口:File
  • 基于网络操作的 I/O 接口:Socket

虽然 Socket 类并不在java.io包下,但是我们仍然把它们划分在一起,因为 I/O 的核心问题,要么是数据格式影响 I/O 操作,要么是传输方式影响 I/O 操作,也就是将什么样的数据写到什么地方的问题。

I/O 只是人与机器或者机器与机器交互的手段,除了在它们能够完成这个交互功能外,我们关注的就是如何提高它的运行效率,而数据格式和传输方式是影响效率最关键的因素。

下面我们基于这两点,来展开分析!

二、传输格式的分类

从传输格式角度看,可以分两类:字节流和字符流。

  • 基于字节的输入和输出操作接口分别是:InputStream 和 OutputStream
  • 基于字符的输入和输出操作接口分别是:Reader 和 Writer 。

2.1、字节流接口

字节流,是 I/O 流中最底层的流,能处理任何类型的数据传输,比如文字、图片、视频、文件等。

2.1.1、基于字节输入流的接口

打开 JDK 源码,整理之后,InputStream 输入流接口的类继承层次如下图所示:

这些输入流类,根据角色不同,还可以进行分类,分为:节点流和处理流。

  • 节点流:指的是向指定的设备,比如磁盘、网络,进行读/写数据,也被称为底层流,直接和数据源相接
  • 处理流:指的是在已存在的节点流或者处理流基础上,包装一些更加方便操作 io 流的功能,比如压缩、序列化、缓冲操作等,也被称为包装流

输入流类,根据角色的划分类别如下:

OutputStream 输出流的类层次结构也是类似。

2.1.2、基于字节输出流的接口

OutputStream 输入流接口的类继承层次如下图所示:

字节输出流类,根据角色的划分类别如下:

这里就不详细的介绍各个子类的使用方法,有兴趣的朋友可以查看 JDK 的 API 说明文档,笔者也会在后期的系列文章会进行详细的介绍。

这里只是重点想说一下,无论是输入还是输出,操作数据的方式可以组合使用,各个处理流的类并不是只操作固定的节点流,比如如下输出方式:

//将文件输出流包装到序列化输出流中,再将序列化输出流包装到缓冲中
OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream(new File("fileName")))

另外,输出流最终写到什么地方必须要指定,要么是写到硬盘中,要么是写到网络中,从图中可以发现,写网络实际上也是写文件,只不过写到网络中,需要经过底层操作系统将数据发送到其他指定的计算机中,而不是写入到本地硬盘中。

2.2、字符流接口

不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作的都是字节而不是字符

那为什么要有操作字符的 I/O 接口呢?

这是因为我们的程序中通常操作的数据都是以字符形式,为了程序操作更方便而提供一个直接写字符的 I/O 接口,仅此而已

除此之外,使用字节流操控文字时不是很方便,容易乱码,由此诞生了不同的字符集以及对应的字符编码规则

由于全世界的文字博大精深,不同的字符集,占用的字节位数不同,以中文为例,在GBK编码规则中,一个中文使用二个字节存储;而在UTF-8编码规则中,一个中文使用三个字节存储,如果写入和读取的编码规则不一样,读取的字节数很容易裂开,导致出现乱码。

比如以下案例:

public static void main(String[] args) throws Exception {byte[] bytes = "学习Java语言".getBytes("ISO8859-1");File file = new File("encoding.txt");OutputStream out = new FileOutputStream(file);out.write(bytes);out.close();
}

文件的内容如下:

??Java??

为了更方便地处理中文这些字符,计算机就推出了字符编码规则。

实现原理:字节流 + 编码表。

  • 当写入一段文字时,会使用指定的字符集,将该 String 编码为一系列字节,将结果存储到新的字节数组中,进行传输
  • 当读取一段文字时,通过指定的字符集,解码指定的字节数组来构造新的 String,从而解决文字乱码的问题。
2.2.1、基于字符输入流的接口

Reader 输入流接口的类继承层次如下图所示:

同样的,字符输入流类,根据角色的划分类别如下:

2.2.2、基于字符输出流的接口

Writer 输出流的类继承层次如下图所示:

字符输出流类,根据角色的划分类别如下:

2.3、字节与字符的转化

刚刚我们说到,不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,设计字符的原因是为了程序更方便的操作文本。

那么怎么将字符转化成字节或者将字节转化成字符呢?

其中,InputStreamReaderOutputStreamWriter就是转化桥梁。

2.3.1、输入流转换方案

输入流字符解码相关类结构的转化过程如下图所示:

从图上可以看到,InputStreamReader类是字节到字符的转化桥梁,
其中StreamDecoder指的是一个解码操作类,Charset指的是字符集。

InputStreamReader的过程需要指定编码字符集,否则将采用操作系统默认字符集,很可能会出现乱码问题,StreamDecoder则是完成字节到字符的解码的实现类。

案例如下:

File file = new File("encoding.txt");
FileInputStream inputStream =new FileInputStream(file);
//字节输入流转为字符输入流
InputStreamReader streamReader =new InputStreamReader(inputStream, Charset.forName("UTF-8"));
2.3.2、输出流转换方案

输出流转化过程也是类似,如下图所示:

通过OutputStreamWriter类完成字符到字节的编码过程,由StreamEncoder 完成编码过程。

案例如下:

File file = new File("output.txt");
FileOutputStream outputStream =new FileOutputStream(file);
//字符输出流转字节输出流
OutputStreamWriter streamWriter =new OutputStreamWriter(outputStream, Charset.forName("UTF-8"));

三、传输方式的分类

上文我们介绍了数据的传输格式,可以通过字节流和字符流接口来完成数据的传输,至于数据写到何处,主要取决于数据的传输方式。

从传输方式角度看,可以分两类:磁盘和网络。

  • 基于磁盘操作的操作接口是:File
  • 基于网络操作的操作接口是:Socket
3.1、文件接口

我们知道数据在磁盘的唯一最小描述就是文件,也就是说上层应用程序只能通过文件来操作磁盘上的数据,文件也是操作系统和磁盘驱动器交互的一个最小单元。

在 Java I/O 体系中,File类是唯一代表磁盘文件本身的对象

File 类定义了一些与平台无关的方法来操作文件,包括检查一个文件是否存在、创建、删除文件、重命名文件、判断文件的读写权限是否存在、设置和查询文件的最近修改时间等等操作。

值得注意的是 Java 中通常的 File 并不代表一个真实存在的文件对象,当你通过指定一个路径描述符时,它就会返回一个代表这个路径相关联的一个虚拟对象,这个可能是一个真实存在的文件或者是一个包含多个文件的目录。

例如,读取一个文件内容,程序如下:

public static void main(String[] args) throws Exception {StringBuilder str = new StringBuilder();char[] buf = new char[1024];// 读取文件的内容FileReader f = new FileReader("input.txt");while(f.read(buf)>0){str.append(buf);}str.toString();
}

以上面的程序为例,从硬盘中读取一段文本字符,操作流程如下图:

当我们传入一个指定的文件名来创建File对象,通过FileReader来读取文件内容时,会自动创建一个FileInputStream对象来读取文件内容,也就是我们上文中所说的字节流来读取文件。

紧接着,会创建一个FileDescriptor的对象,其实这个对象就是真正代表一个存在的文件对象的描述。

由于我们需要读取的是字符格式,所以需要StreamDecoder类通过解码方法decode,将字节转字符,至于如何从磁盘驱动器上读取一段数据,由操作系统帮我们完成。

3.2、网络接口

继续来说说数据传输的另一种处理方式:网络通信。

3.2.1、Socket 简介

在 Java 网络体系中,Socket是描述计算机之间完成相互通信一种抽象定义。

光从描述看可能很难理解,打个比方,可以把Socket比作为两个城市之间的交通工具,有了它,就可以在城市之间来回穿梭了;并且,交通工具有多种,每种交通工具也有相应的交通规则。

Socket 也一样,也有多种,大部分情况下我们使用的都是基于 TCP/IP 的流套接字,它是一种稳定的通信协议。

比较典型的基于 Socket 通信的应用程序场景,如下图:

主机 A 的应用程序要想和主机 B 的应用程序通信,必须通过 Socket 建立连接,而建立 Socket 连接必须需要底层 TCP/IP 协议来建立 TCP 连接。

3.2.2、建立通信链路

我们知道网络层使用的 IP 协议可以帮助我们根据 IP 地址来找到目标主机,但是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通信呢?

这个时候需要通过 TCP 或 UPD 协议,也就是指定对应的端口号

通过 IP + 端口号,就可以创建一个代表唯一一个主机上的一个应用程序的通信链路了,创建后的通信链路我们称它为 Socket 实例。

以 TCP 协议为例,为了准确无误地把数据送达目标处,TCP 协议采用了三次握手策略,如下图:

其中,SYN 全称为 Synchronize Sequence Numbers,表示同步序列编号,是 TCP/IP 建立连接时使用的握手信号。

ACK 全称为 Acknowledge character,即确认字符,表示发来的数据已确认接收无误。

在客户机和客户机之间建立正常的 TCP 网络连接时,发送端首先发出一个 SYN 消息,接收端使用 SYN + ACK 应答表示接收到了这个消息,最后发送端再以 ACK 消息响应。

整体流程如下:

  • 发送端 –(发送带有 SYN 标志的数据包 )–> 接受端(第一次握手);
  • 接受端 –(发送带有 SYN + ACK 标志的数据包)–> 发送端(第二次握手);
  • 发送端 –(发送带有 ACK 标志的数据包) –> 接受端(第三次握手);

完成三次握手之后,发送端和接收端之间建立起可靠的 TCP 连接,客户端应用程序与服务器应用程序就可以开始传送数据了。

3.2.3、传输数据

当客户端要与服务端通信时,客户端首先要创建一个 Socket 实例,也就是指定目标服务器的 IP 和端口。

默认操作系统将为这个 Socket 实例分配一个没有被使用的本地端口号,并创建一个包含本地、远程地址和端口号的套接字数据结构,这个数据结构将一直保存在系统中直到这个连接关闭。

  • 客户端简单示例
public static void main(String[] args) throws IOException {//通过IP和端口与服务端建立连接Socket socket =new Socket("127.0.0.1",8080);//将字符流转化成字节流,并输出BufferedWriter bufferedWriter =new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));String str="Hello,我是客户端!";bufferedWriter.write(str);bufferedWriter.flush();bufferedWriter.close();
}
  • 服务端简单示例
public static void main(String[] args) throws Exception {//初始化服务端socket并且绑定 8080 端口ServerSocket serverSocket = new ServerSocket(8080);//循环监听所有连接的客户端请求while (true){try {//等待客户端的连接Socket socket = serverSocket.accept();//将字节流转化成字符流,读取客户端输入的内容BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(socket.getInputStream()));//读取一行数据String str = bufferedReader.readLine();//输出打印System.out.println("服务端收到客户端发送的信息:" + str);} catch (Exception e) {}}
}

我们先启动服务端程序,再运行客户端,服务端收到客户端发送的信息,打印结果如下:

服务端收到客户端发送的信息:Hello,我是客户端!

注意,客户端只有与服务端建立三次握手成功之后,才会发送数据,而 TCP/IP 握手过程,底层操作系统已经帮我们实现了!

当连接已经建立成功,服务端和客户端都会拥有一个Socket实例,每个Socket实例都有一个InputStreamOutputStream,正如我们前面所说的,网络 I/O 都是以字节流传输的,Socket正是通过这两个对象来交换数据。

Socket对象创建时,操作系统同时将会为InputStreamOutputStream分别分配一定大小的缓冲区,数据的写入和读取都是通过这个缓存区完成的。

发送端将数据写到OutputStream对应的SendQ队列中,当队列填满时,数据将被发送到另一端InputStreamRecvQ队列中,如果这时RecvQ已经满了,那么OutputStreamwrite方法将会阻塞直到RecvQ队列有足够的空间容纳SendQ发送的数据。

值得特别注意的是,缓存区的大小以及写入端的速度和读取端的速度非常影响这个连接的数据传输效率,由于可能会发生阻塞,所以网络 I/O 和磁盘 I/O 在数据的写入和读取还要有一个协调的过程,如果两边同时传送数据,可能会产生死锁的问题。

如何提高网络 IO 传输效率、保证数据传输的可靠,这个我们后面单独开篇进行讲解。

四、小结

本文阐述的内容较多,整合了很多有用的信息,从 Java 基本的 I/O 类库结构开始说起,主要介绍了 IO 的传输格式和传输方式,包括字节流和字符流接口相关的分类介绍,以及磁盘 I/O 和网络 I/O 的基本工作方式。

内容难免有所遗漏,和理解不到的位置,欢迎网友留言指出!

五、参考

1、https://developer.ibm.com/zh/articles/j-lo-javaio/

六、写到最后

最近无意间获得一份阿里大佬写的技术笔记,内容涵盖 Spring、Spring Boot/Cloud、Dubbo、JVM、集合、多线程、JPA、MyBatis、MySQL 等技术知识。需要的小伙伴可以点击如下链接获取,资源地址:技术资料笔记。

不会有人刷到这里还想白嫖吧?点赞对我真的非常重要!在线求赞。加个关注我会非常感激!

相关文章:

IO系列(一) -一文带你读懂 java 中的IO流!

一、摘要 说到 IO,相信大家都不陌生,英文全称:Input/Output,即输入/输出,通常指数据在内部存储器和外部存储器或其他周边设备之间的输入和输出。 比如我们常用的SD卡、U盘、移动硬盘等等存储文件的硬件设备&#xff…...

代码随想录算法训练营第六天| 242. 有效的字母异位词、349. 两个数组的交集、202. 快乐数、1. 两数之和

哈希表理论基础 [LeetCode] 242. 有效的字母异位词 [LeetCode] 242. 有效的字母异位词 文章解释 [LeetCode] 242. 有效的字母异位词 视频解释 题目: 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 注意:若 s 和 t 中每个字符出…...

【python】中的可迭代对象、迭代器、生成器

结论 凡是实现了__iter__() 方法的类都称之为可迭代对象,但 __iter__() 方法的返回值只能是迭代器和生成器for 循环的本质是先调用 __iter__() 方法,然后不断调用返回值的 __next__() 方法,直至报出异常 StopIteration,可迭代对象…...

短视频矩阵系统源码/saas--总后台端、商户端、代理端、源头开发

短视频矩阵系统源码/saas--总后台端、商户端、代理端、源头开发 搭建短视频矩阵系统源码的交付步骤可以概括为以下几个关键环节: 1. **系统需求分析**:明确系统需要支持的功能,如短视频的上传、存储、播放、分享、评论、点赞等。 2. **技术选…...

K8s:二进制安装k8s(单台master)

目录 一、安装k8s 1、拓扑图 2、系统初始化配置 2.1关闭防火墙selinx以及swap 2.2设置主机名 2.3在每台主机中添加hosts,做映射 2.4调整内核参数,将桥接的ipv4流量传递到iptables,关闭ipv6 2.4时间同步 3、部署docker引擎&#xff0…...

C++类和对象下——实现日期类

前言 在学习了类和对象的六大成员函数后,为了巩固我们学习的知识可以手写一个日期类来帮助我们理解类和对象,加深对于其的了解。 默认函数 构造函数 既然是写类和对象,我们首先就要定义一个类,然后根据实际需要来加入类的数据与函…...

252 基于MATLAB的自适应差分阈值法检测心电信号的QRS波

基于MATLAB的自适应差分阈值法检测心电信号的QRS波,QRS波群反映左、右心室除极电位和时间的变化,第一个向下的波为Q波,向上的波为R波,接着向下的波是S波。通过GUI进行数据处理,展示心率和QRS。程序已调通,可…...

SSIM(Structural Similarity),结构相似性及MATLAB实现

参考文献 Wang, Zhou; Bovik, A.C.; Sheikh, H.R.; Simoncelli, E.P. (2004-04-01). “Image quality assessment: from error visibility to structural similarity”. IEEE Transactions on Image Processing. 13 (4): 600–612. Bibcode:2004ITIP…13…600W. CiteSeerX 10.…...

第十六章-消费者-PUSH方式(一)

16.1 准备阶段 先从一段官方示例代码开始 public class Consumer {public static void main(String[] args) throws InterruptedException, MQClientException {// 初始化consumer,并设置consumer group nameDefaultMQPushConsumer consumer new DefaultMQPushCo…...

【C++要哮着学】初识C++,什么是C++?什么是命名空间?什么又是缺省函数?

文章目录 前言1、C简介1.1、什么是C1.2、C起源1.3、C发展 2、C关键字(C98)3、命名空间3.1、命名空间的定义及使用3.2、命名空间的嵌套3.3、命名空间的三种使用方式3.3.1、加命名空间名称及作用域限定符3.3.2、使用using将命名空间中某个成员引入3.3.3、使…...

Lua 数字格式化

在编程中,对数字进行格式化是一项常见的任务,特别是当我们需要在用户界面中显示数据或生成报告时。在 Lua 中,我们可以使用一些简单而有效的函数来实现数字的格式化。在本文中,我们将介绍一个由几个函数组成的小型 Lua 库&#xf…...

Java入门基础学习笔记13——数据类型

数据类型的分类: 基本数据类型 引用数据类型 基本数据类型:4大类8种类型: 定义整形用int,再大的数用long。 package cn.ensource.variable;public class VariableDemo2 {public static void main(String[] args) {//目标&#x…...

使用Docker+Jar方式部署微服务工程(前后端分离)看着一篇就够了

本篇教程的使用到的技术有springboot、springcloud、Nacos、Docker、Nginx部署前后端分离访问的微服务。 部署一下Nacos 首先我们需要在服务器中(或者本地部署启动一下Nacos),这里我采用服务器的方式进行部署,这里有一点不一样的…...

红外遥控和LCD1602

26.1.1 红外线简介 人的眼睛能看到的可见光按波长从长到短排列,依次为红、橙、黄、绿、青、蓝、紫。其中红光的波长范围为 0.62~0.76μm;紫光的波长范围为 0.38~0.46μm。比紫光波长还短的光叫紫外线,比红光波长还长的…...

房屋出租管理系统需求分析及功能介绍

房屋租赁管理系统适用于写字楼、办公楼、厂区、园区、商城、公寓等商办商业不动产的租赁管理及租赁营销;提供资产管理,合同管理,租赁管理, 物业管理,门禁管理等一体化的运营管理平台,提高项目方管理运营效率…...

高精度模拟算法

高精度模拟算法 高精度加法 extern string m,n; extern int a[MAX],b[MAX],ans[MAX]; void addition(){int _mmax(m.size(),n.size());reverse(m.begin(),m.end()),reverse(n.begin(),n.end());//转置原字符串for(int i0;i<m.size();i) a[i]m[i]-0;//字符型以ASCII码存储&…...

Ansible简介版

目录 架构 环境部署 一、Ansible安装部署 1.yum安装Ansible 2.修改主机清单文件 3.配置密钥对验证 4.ansible-doc 5.看被控主机 二、常用模块 1.Command模块 2.Shell模块 3.Cron模块 1.添加 2.删除 4.User模块 5.Group模块 1.创建组 ​编辑 ​编辑 ​编辑…...

卷积通用模型的剪枝、蒸馏---蒸馏篇--RKD关系蒸馏(以deeplabv3+为例)

本文使用RKD实现对deeplabv3+模型的蒸馏;与上一篇KD蒸馏的方法有所不同,RKD是对展平层的特征做蒸馏,蒸馏的loss分为二阶的距离损失Distance-wise Loss和三阶的角度损失Angle-wise Loss。 完整代码放在文末。 一、RKD简介 RKD算法的核心是以教师模型的多个输出为结构单元,取…...

AVL树的完全指南:平衡与性能

文章目录 AVL树简介AVL的操作建立一个AVL树插入操作删除操作 书写代码1.构造函数和析构函数2.获取最大值和最小值3.树的高度和节点个数3.前序中序和后序遍历4.判断树是否为空树5.四个旋转操作6.获取平衡因子7.插入操作8.删除操作9.搜索节点.h文件中的定义 总结 AVL树简介 AVL树…...

itext7 PDF添加水印,获取页面高度,添加到页面右上角

ps: pdf添加水印&#xff0c;内容多的时候会往下跑&#xff0c;修改为获取当前页面高度&#xff0c;进行固定在顶部&#xff0c;其他需要可以自己进行调整&#xff0c;直接贴代码。 public static void main(String[] args) throws IOException {String localFilePath "…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...