Java IO 体系

Java IO 是一个庞大的知识体系,很多人学着学着就会学懵了,包括我在内也是如此,所以本文将会从 Java 的 BIO 开始,一步一步深入学习,引出 JDK1.4 之后出现的 NIO 技术,对比 NIO 与 BIO 的区别,然后对 NIO 中重要的三个组成部分进行讲解(缓冲区、通道、选择器),最后实现一个简易的客户端与服务器通信功能

BIO NIO 和 AIO 的区别

我们会以一个经典的烧开水的例子通俗地讲解它们之间的区别

类型 烧开水
BIO 一直监测着某个水壶,该水壶烧开水后再监测下一个水壶
NIO 每隔一段时间就看看所有水壶的状态,哪个水壶烧开水就去处理哪个水壶
AIO 不用监测水壶,每个水壶烧开水后都会主动通知线程说:“我的水烧开了,来处理我吧”

BIO (同步阻塞 I/O)

这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 小菠萝一直看着着这个水壶,直到这个水壶烧开,才去处理下一个水壶。线程在等待水壶烧开的时间段什么都没有做。

NIO(同步非阻塞 I/O)

还拿烧开水来说,NIO的做法是小菠萝一边玩着手机,每隔一段时间就看一看每个水壶的状态,看看是否有水壶的状态发生了改变,如果某个水壶烧开了,可以先处理那个水壶,然后继续玩手机,继续隔一段时间又看看每个水壶的状态。

AIO (异步非阻塞 I/O)

小菠萝觉得每隔一段时间就去看一看水壶太费劲了,于是购买了一批烧开水时可以哔哔响的水壶,于是开始烧水后,小菠萝就直接去客厅玩手机了,水烧开时,就发出“哔哔”的响声,通知小菠萝来关掉水壶

传统的 BIO

Java IO流是一个庞大的生态环境,其内部提供了很多不同的输入流和输出流,细分下去还有字节流和字符流,甚至还有缓冲流提高 IO 性能,转换流将字节流转换为字符流······看到这些就已经对 IO 产生恐惧了,在日常开发中少不了对文件的 IO 操作,虽然 apache 已经提供了 Commons IO 这种封装好的组件,但面对特殊场景时,我们仍需要自己去封装一个高性能的文件 IO 工具类,本文将会解析 Java IO 中涉及到的各个类,以及讲解如何正确、高效地使用它们。

什么是流

知识科普:我们知道任何一个文件都是以二进制形式存在于设备中,计算机就只有 01,你能看见的东西全部都是由这两个数字组成,你看这篇文章时,这篇文章也是由01组成,只不过这些二进制串经过各种转换演变成一个个文字、一张张图片跃然屏幕上。

就是将这些二进制串在各种设备之间进行传输,如果你觉得有些抽象,我举个例子就会好理解一些:

下图是一张图片,它由01串组成,我们可以通过程序把一张图片拷贝到一个文件夹中,

把图片转化成二进制数据集,把数据一点一点地传递到文件夹中 , 类似于水的流动 , 这样整体的数据就是一个数据流

未命名绘图

IO 流读写数据的特点:

  • 顺序读写。读写数据时,大部分情况下都是按照顺序读写,读取时从文件开头的第一个字节到最后一个字节,写出时也是也如此(RandomAccessFile 可以实现随机读写)
  • 字节数组。读写数据时本质上都是对字节数组做读取和写出操作,即使是字符流,也是在字节流基础上转化为一个个字符,所以字节数组是 IO 流读写数据的本质。

流的分类

根据数据流向不同分类:输入流 和 输出流

  • 输入流:从磁盘或者其它设备中将数据输入到进程中
  • 输出流:将进程中的数据输出到磁盘或其它设备上保存

1

图示中的硬盘只是其中一种设备,还有非常多的设备都可以应用在IO流中,例如:打印机、硬盘、显示器、手机······

根据处理数据的基本单位不同分类:字节流 和 字符流

  • 字节流:以**字节(8 bit)**为单位做数据的传输
  • 字符流:以字符为单位(1字符 = 2字节)做数据的传输

字符流的本质也是通过字节流读取,Java 中的字符采用 Unicode 标准,在读取和输出的过程中,通过以字符为单位,查找对应的码表将字节转换为对应的字符。

面对字节流和字符流,很多读者都有疑惑:什么时候需要用字节流,什么时候又要用字符流?

我这里做一个简单的概括,你可以按照这个标准去使用:

字符流只针对字符数据进行传输,所以如果是文本数据,优先采用字符流传输;除此之外,其它类型的数据(图片、音频等),最好还是以字节流传输。

根据这两种不同的分类,我们就可以做出下面这个表格,里面包含了 IO 中最核心的 4 个顶层抽象类:

数据流向 / 数据类型 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

现在看 IO 是不是有一些思路了,不会觉得很混乱了,我们来看这四个类下的所有成员。

image-20200823091738251

[来自于 cxuan 的 《Java基础核心总结》]

看到这么多的类是不是又开始觉得混乱了,不要慌,字节流和字符流下的输入流和输出流大部分都是一一对应的,有了上面的表格支撑,我们不需要再担心看见某个类会懵逼的情况了。

看到 Stream 就知道是字节流,看到 Reader / Writer 就知道是字符流

这里还要额外补充一点:Java IO 提供了字节流转换为字符流的转换类,称为转换流。

转换流 / 数据类型 字节流与字符流之间的转换
(输入)字节流 => 字符流 InputStreamReader
(输出)字符流 => 字节流 OutputStreamWriter

注意字节流与字符流之间的转换是有严格定义的:

  • 输入流:可以将字节流 => 字符流
  • 输出流:可以将字符流 => 字节流

为什么在输入流不能字符流 => 字节流,输出流不能字节流 => 字符流?

在存储设备上,所有数据都是以字节为单位存储的,所以输入到内存时必定是以字节为单位输入,输出到存储设备时必须是以字节为单位输出,字节流才是计算机最根本的存储方式,而字符流是在字节流的基础上对数据进行转换,输出字符,但每个字符依旧是以字节为单位存储的。

节点流和处理流

在这里需要额外插入一个小节讲解节点流和处理流。

  • 节点流:节点流是真正传输数据的流对象,用于向特定的一个地方(节点)读写数据,称为节点流。例如 FileInputStream
  • 处理流:处理流是对节点流的封装,使用外层的处理流读写数据,本质上是利用节点流的功能,外层的处理流可以提供额外的功能。处理流的基类都是以 Filter 开头。

1

上图将 ByteArrayInputStream 封装成 DataInputStream,可以将输入的字节数组转换为对应数据类型的数据。例如希望读入int类型数据,就会以2个字节为单位转换为一个数字。

Java IO 的核心类 File

Java 提供了 File类,它指向计算机操作系统中的文件和目录,通过该类只能访问文件和目录,无法访问内容。 它内部主要提供了 3 种操作:

  • 访问文件的属性:绝对路径、相对路径、文件名······
  • 文件检测:是否文件、是否目录、文件是否存在、文件的读/写/执行权限······
  • 操作文件:创建目录、创建文件、删除文件······

上面举例的操作都是在开发中非常常用的,File 类远不止这些操作,更多的操作可以直接去 API 文档中根据需求查找。

访问文件的属性:

API 功能
String getAbsolutePath() 返回该文件处于系统中的绝对路径名
String getPath() 返回该文件的相对路径,通常与 new File() 传入的路径相同
String getName() 返回该文件的文件名

文件检测:

API 功能
boolean isFIle() 校验该路径指向是否一个文件
boolean isDirectory() 校验该路径指向是否一个目录
boolean isExist() 校验该路径指向的文件/目录是否存在
boolean canWrite() 校验该文件是否可写
boolean canRead() 校验该文件是否可读
boolean canExecute() 校验该文件/目录是否可以被执行

操作文件:

API 功能
mkdirs() 递归创建多个文件夹,路径中间有可能某些文件夹不存在
createNewFile() 创建新文件,它是一个原子操作,有两步:检查文件是否存在、创建新文件
delete() 删除文件或目录,删除目录时必须保证该目录为空

多了解一些

文件的读/写/执行权限,在 Windows 中通常表现不出来,而在 Linux 中可以很好地体现这一点,原因是 Linux 有严格的用户权限分组,不同分组下的用户对文件有不同的操作权限,所以这些方法在 Linux 下会比在 Windows 下更好理解。下图是 redis 文件夹中的一些文件的详细信息,被红框标注的是不同用户的执行权限:

  • r(Read):代表该文件可以被当前用户读,操作权限的序号是 4
  • w(Write):代表该文件可以被当前用户写,操作权限的序号是 2
  • x(Execute):该文件可以被当前用户执行,操作权限的序号是 1

image-20200825080020253

root root 分别代表:当前文件的所有者当前文件所属的用户分组。Linux 下文件的操作权限分为三种用户:

  • 文件所有者:拥有的权限是红框中的前三个字母-代表没有某个权限
  • 文件所在组的所有用户:拥有的权限是红框中的中间三个字母
  • 其它组的所有用户:拥有的权限是红框中的最后三个字母

Java IO 流对象

回顾流的分类有2种:

  • 根据数据流向分为输入流和输出流
  • 根据数据类型分为字节流和字符流

所以,本小节将以字节流和字符流作为主要分割点,在其内部再细分为输入流和输出流进行讲解。

image-20200823091738251

字节流对象

字节流对象大部分输入流和输出流都是成双成对地出现,所以学习的时候可以将输入流和输出流一一对应的流对象关联起来,输入流和输出流只是数据流向不同,而处理数据的方式可以是相同的。

注意不要认为用什么流读入数据,就需要用对应的流写出数据,在 Java 中没有这么规定,下图只是各个对象之间的一个对应关系,不是两个类使用时必须强制关联使用

下面有非常多的类,我会介绍基类的方法,了解这些方法是非常有必要的,子类的功能基于父类去扩展,只有真正了解父类在做什么,学习子类的成本就会下降。

image-20200825084204026

InputStream

InputStream 是字节输入流的抽象基类,提供了通用的读方法,让子类使用或重写它们。下面是 InputStream 常用的重要的方法。

重要方法 功能
public abstract int read() 从输入流中读取下一个字节,读到尾部时返回 -1
public int read(byte b[]) 从输入流中读取长度为 b.length 个字节放入字节数组 b 中
public int read(byte b[], int off, int len) 从输入流中读取指定范围的字节数据放入字节数组 b 中
public void close() 关闭此输入流并释放与该输入流相关的所有资源

还有其它一些不太常用的方法,我也列出来了。

其它方法 功能
public long skip(long n) 跳过接下来的 n 个字节,返回实际上跳过的字节数
public long available() 返回下一次可读取(跳过)且不会被方法阻塞的字节数的估计值
public synchronized void mark(int readlimit) 标记此输入流的当前位置,对 reset() 方法的后续调用将会重新定位在 mark() 标记的位置,可以重新读取相同的字节
public boolean markSupported() 判断该输入流是否支持 mark() 和 reset() 方法,即能否重复读取字节
public synchronized void reset() 将流的位置重新定位在最后一次调用 mark() 方法时的位置

image-20200827082445395

(1)ByteArrayInputStream

ByteArrayInputStream 内部包含一个 buf 字节数组缓冲区,该缓冲区可以从流中读取的字节数,使用 pos 指针指向读取下一个字节的下标位置,内部还维护了一个count 属性,代表能够读取 count 个字节。

bytearrayinputstream

必须保证 pos 严格小于 count,而 count 严格小于 buf.length 时,才能够从缓冲区中读取数据

(2)FileInputStream

文件输入流,从文件中读入字节,通常对文件的拷贝、移动等操作,可以使用该输入流把文件的字节读入内存中,然后再利用输出流输出到指定的位置上。

(3)PipedInputStream

管道输入流,它与 PipedOutputStream 成对出现,可以实现多线程中的管道通信。PipedOutputStream 中指定与特定的 PipedInputStream 连接,PipedInputStream 也需要指定特定的 PipedOutputStream 连接,之后输出流不断地往输入流的 buffer 缓冲区写数据,而输入流可以从缓冲区中读取数据。

(4)ObjectInputStream

对象输入流,用于对象的反序列化,将读入的字节数据反序列化为一个对象,实现对象的持久化存储。

(5)PushBackInputStream

它是 FilterInputStream 的子类,是一个处理流,它内部维护了一个缓冲数组buf

  • 在读入字节的过程中可以将读取到的字节数据回退给缓冲区中保存,下次可以再次从缓冲区中读出该字节数据。所以PushBackInputStream 允许多次读取输入流的字节数据,只要将读到的字节放回缓冲区即可。

2

需要注意的是如果回推字节时,如果缓冲区已满,会抛出 IOException 异常。

它的应用场景:对数据进行分类规整

假如一个文件中存储了数字字母两种类型的数据,我们需要将它们交给两种线程各自去收集自己负责的数据,如果采用传统的做法,把所有的数据全部读入内存中,再将数据进行分离,面对大文件的情况下,例如1G、2G,传统的输入流在读入数组后,由于没有缓冲区,只能对数据进行抛弃,这样每个线程都要读一遍文件

使用 PushBackInputStream 可以让一个专门的线程读取文件,唤醒不同的线程读取字符:

  • 第一次读取缓冲区的数据,判断该数据由哪些线程读取
  • 回退数据,唤醒对应的线程读取数据
  • 重复前两步
  • 关闭输入流

到这里,你是否会想到 AQSCondition 等待队列,多个线程可以在不同的条件上等待被唤醒。

(6)BufferedInputStream

缓冲流,它是一种处理流,对节点流进行封装并增强,其内部拥有一个 buffer 缓冲区,用于缓存所有读入的字节,当缓冲区满时,才会将所有字节发送给客户端读取,而不是每次都只发送一部分数据,提高了效率。

(7)DataInputStream

数据输入流,它同样是一种处理流,对节点流进行封装后,能够在内部对读入的字节转换为对应的 Java 基本数据类型。

(8)SequenceInputStream

将两个或多个输入流看作是一个输入流依次读取,该类的存在与否并不影响整个 IO 生态,在程序中也能够做到这种效果

(9)StringBufferInputStream

将字符串中每个字符的低 8 位转换为字节读入到字节数组中,目前已过期

InputStream 总结:

  • InputStream 是所有输入字节流的抽象基类
  • ByteArrayInputStream 和 FileInputStream 是两种基本的节点流,他们分别从字节数组本地文件中读取数据
  • DataInputStream、BufferedInputStream 和 PushBackInputStream 都是处理流,对基本的节点流进行封装并增强
  • PipiedInputStream 用于多线程通信,可以与其它线程公用一个管道,读取管道中的数据。
  • ObjectInputStream 用于对象的反序列化,将对象的字节数据读入内存中,通过该流对象可以将字节数据转换成对应的对象

OutputStream

OutputStream 是字节输出流的抽象基类,提供了通用的写方法,让继承的子类重写和复用。

方法 功能
public abstract void write(int b) 将指定的字节写出到输出流,写入的字节是参数 b 的低 8 位
public void write(byte b[]) 将指定字节数组中的所有字节写入到输出流当中
public void write(byte b[], int off, int len) 指定写入的起始位置 offer,字节数为 len 的字节数组写入到输出流当中
public void flush() 刷新此输出流,并强制写出所有缓冲的输出字节到指定位置,每次写完都要调用
public void close() 关闭此输出流并释放与此流关联的所有系统资源

image-20200827090101687

OutputStream 中大多数的类和 InputStream 是对应的,只不过数据的流向不同而已。从上面的图可以看出:

  • OutputStream 是所有输出字节流的抽象基类

  • ByteArrayOutputStream 和 FileOutputStream 是两种基本的节点流,它们分别向字节数组本地文件写出数据

  • DataOutputStream、BufferedOutputStream 是处理流,前者可以将字节数据转换成基本数据类型写出到文件中;后者是缓冲字节数组,只有在缓冲区满时,才会将所有的字节写出到目的地,减少了 IO 次数

  • PipedOutputStream 用于多线程通信,可以和其它线程共用一个管道,向管道中写入数据

  • ObjectOutputStream 用于对象的序列化,将对象转换成字节数组后,将所有的字节都写入到指定位置中

  • PrintStream 在 OutputStream 基础之上提供了增强的功能,即可以方便地输出各种类型的数据(而不仅限于byte型)的格式化表示形式,且 PrintStream 的方法从不抛出 IOEception,其原理是写出时将各个数据类型的数据统一转换为 String 类型,我会在讲解完

字符流对象

字符流对象也会有对应关系,大多数的类可以认为是操作的数据从字节数组变为字符,类的功能和字节流对象是相似的。

字符输入流和字节输入流的组成非常相似,字符输入流是对字节输入流的一层转换,所有文件的存储都是字节的存储,在磁盘上保留的不是文件的字符,而是先把字符编码成字节,再保存到文件中。在读取文件时,读入的也是一个一个字节组成的字节序列,而 Java 虚拟机通过将字节序列,按照2个字节为单位转换为 Unicode 字符,实现字节到字符的映射。

image-20200827094740444

Reader

Reader 是字符输入流的抽象基类,它内部的重要方法如下所示。

重要方法 方法功能
public int read(java.nio.CharBuffer target) 将读入的字符存入指定的字符缓冲区中
public int read() 读取一个字符
public int read(char cbuf[]) 读入字符放入整个字符数组中
abstract public int read(char cbuf[], int off, int len) 将字符读入字符数组中的指定范围中

还有其它一些额外的方法,与字节输入流基类提供的方法是相同的,只是作用的对象不再是字节,而是字符。

image-20200827095911702

  • Reader 是所有字符输入流的抽象基类
  • CharArrayReader 和 StringReader 是两种基本的节点流,它们分别从读取 字符数组字符串 数据,StringReader 内部是一个 String 变量值,通过遍历该变量的字符,实现读取字符串,本质上也是在读取字符数组
  • PipedReader 用于多线程中的通信,从共用地管道中读取字符数据
  • BufferedReader 是字符输入缓冲流,将读入的数据放入字符缓冲区中,实现高效地读取字符
  • InputStreamReader 是一种转换流,可以实现从字节流转换为字符流,将字节数据转换为字符

Writer

Reader 是字符输出流的抽象基类,它内部的重要方法如下所示。

重要方法 方法功能
public void write(char cbuf[]) 将 cbuf 字符数组写出到输出流
abstract public void write(char cbuf[], int off, int len) 将指定范围的 cbuf 字符数组写出到输出流
public void write(String str) 将字符串 str 写出到输出流,str 内部也是字符数组
public void write(String str, int off, int len) 将字符串 str 的某一部分写出到输出流
abstract public void flush() 刷新,如果数据保存在缓冲区,调用该方法才会真正写出到指定位置
abstract public void close() 关闭流对象,每次 IO 执行完毕后都需要关闭流对象,释放系统资源

image-20200827104837521

  • Writer 是所有的输出字符流的抽象基类

  • **CharArrayWriter、StringWriter 是两种基本的节点流,它们分别向Char 数组、字符串中写入数据。**StringWriter 内部保存了 StringBuffer 对象,可以实现字符串的动态增长

  • PipedWriter 可以向共用的管道中写入字符数据,给其它线程读取。

  • BufferedWriter缓冲输出流,可以将写出的数据缓存起来,缓冲区满时再调用 flush() 写出数据,减少 IO 次数

  • PrintWriter 和 PrintStream 类似,功能和使用也非常相似,只是写出的数据是字符而不是字节

  • OutputStreamWriter字符流转换为字节流,将字符写出到指定位置

字节流与字符流的转换

从任何地方把数据读入到内存都是先以字节流形式读取,即使是使用字符流去读取数据,依然成立,因为数据永远是以字节的形式存在于互联网和硬件设备中,字符流是通过字符集的映射,才能够将字节转换为字符。

所以 Java 提供了两种转换流:

  • InputStreamReader:从字节流转换为字符流,将字节数据转换为字符数据读入到内存
  • OutputStreamWriter:从字符流转换为字节流,将字符数据转换为字节数据写出到指定位置

了解了 Java 传统的 BIO 中字符流和字节流的主要成员之后,至少要掌握以下两个关键点:

(1)传统的 BIO 是以为基本单位处理数据的,想象成水流,一点点地传输字节数据,IO 流传输的过程永远是以字节形式传输。

(2)字节流和字符流的区别在于操作的数据单位不相同,字符流是通过将字节数据通过字符集映射成对应的字符,字符流本质上也是字节流。

接下来我们再继续学习 NIO 知识,NIO 是当下非常火热的一种 IO 工作方式,它能够解决传统 BIO 的痛点:阻塞

  • BIO 如果遇到 IO 阻塞时,线程将会被挂起,直到 IO 完成后才唤醒线程,线程切换带来了额外的开销。

  • BIO 中每个 IO 都需要有对应的一个线程去专门处理该次 IO 请求,会让服务器的压力迅速提高。

我们希望做到的是当线程等待 IO 完成时能够去完成其它事情,当 IO 完成时线程可以回来继续处理 IO 相关操作,不必干干的坐等 IO 完成。在 IO 处理的过程中,能够有一个专门的线程负责监听这些 IO 操作,通知服务器该如何操作。所以,我们聊到 IO,不得不去接触 NIO 这一块硬骨头。

新潮的 NIO

我们来看看 BIO 和 NIO 的区别,BIO 是面向流的 IO,它建立的通道都是单向的,所以输入和输出流的通道不相同,必须建立2个通道,通道内的都是传输==0101001···==的字节数据。

而在 NIO 中,不再是面向流的 IO 了,而是面向缓冲区,它会建立一个通道(Channel),该通道我们可以理解为铁路,该铁路上可以运输各种货物,而通道上会有一个缓冲区(Buffer)用于存储真正的数据,缓冲区我们可以理解为一辆火车

通道(铁路)只是作为运输数据的一个连接资源,而真正存储数据的是缓冲区(火车)。即通道负责传输,缓冲区负责存储。

理解了上面的图之后,BIO 和 NIO 的主要区别就可以用下面这个表格简单概括。

BIO NIO
面向流(Stream) 面向缓冲区(Buffer)
单向通道 双向通道
阻塞 IO 非阻塞 IO
选择器(Selectors)

缓冲区(Buffer)

缓冲区是存储数据的区域,在 Java 中,缓冲区就是数组,为了可以操作不同数据类型的数据,Java 提供了许多不同类型的缓冲区,除了布尔类型以外,其它基本数据类型都有对应的缓冲区数组对象。

为什么没有布尔类型的缓冲区呢?

在 Java 中,boolean 类型数据只占用 1 bit,而在 IO 传输过程中,都是以字节为单位进行传输的,所以 boolean 的 1 bit 完全可以使用 byte 类型的某一位,或者 int 类型的某一位来表示,没有必要为了这 1 bit 而专门提供多一个缓冲区。

缓冲区 解释
ByteBuffer 存储字节数据的缓冲区
CharBuffer 存储字符数据的缓冲区
ShortBuffer 存储短整型数据的缓冲区
IntBuffer 存储整型数据的缓冲区
LongBuffer 存储长整型数据的缓冲区
FloatBuffer 存储单精度浮点型数据的缓冲区
DoubleBuffer 存储双精度浮点型数据的缓冲区

分配一个缓冲区的方式都高度一致:使用allocate(int capacity)方法。

例如需要分配一个 1024 大小的字节数组,代码就是下面这样子。

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

缓冲区读写数据的两个核心方法:

  • put():将数据写入到缓冲区中
  • get():从缓冲区中读取数据

缓冲区的重要属性:

  • capacity:缓冲区中最大存储数据的容量,一旦声明则无法改变

  • limit:表示缓冲区中可以操作数据的大小,limit 之后的数据无法进行读写。必须满足 limit <= capacity

  • position:当前缓冲区中正在操作数据的下标位置,必须满足 position <= limit

  • mark:标记位置,调用 reset() 将 position 位置调整到 mark 属性指向的下标位置,实现多次读取数据

缓冲区为高效读写数据而提供的其它辅助方法

  • flip():可以实现读写模式的切换,我们可以看看里面的源码
public final Buffer flip() {limit = position;position = 0;mark = -1;return this;
}

调用 flip() 会将可操作的大小 limit 设置为当前写的位置,操作数据的起始位置 position 设置为 0,即从头开始读取数据

  • rewind():可以将 position 位置设置为 0,再次读取缓冲区中的数据
  • clear():清空整个缓冲区,它会将 position 设置为 0,limit 设置为 capacity,可以写整个缓冲区

更多的方法可以去查阅 API 文档,本文碍于篇幅原因就不贴出其它方法了,主要是要理解缓冲区的作用

我们来看一个简单的例子

public Class Main {public static void main(String[] args) {// 分配内存大小为11的整型缓存区IntBuffer buffer = IntBuffer.allocate(11);// 往buffer里写入2个整型数据for (int i = 0; i < 2; ++i) {int randomNum = new SecureRandom().nextInt();buffer.put(randomNum);}// 将Buffer从写模式切换到读模式buffer.flip();System.out.println("position >> " + buffer.position()+ "limit >> " + buffer.limit() + "capacity >> " + buffer.capacity());// 读取buffer里的数据while (buffer.hasRemaining()) {System.out.println(buffer.get());}System.out.println("position >> " + buffer.position()+ "limit >> " + buffer.limit() + "capacity >> " + buffer.capacity());}
}

执行结果如下图所示,首先我们往缓冲区中写入 2 个数据,position 在写模式下指向下标 2,然后调用 flip() 方法切换为读模式,limit 指向下标 2,position 从 0 开始读数据,读到下标为 2 时发现到达 limit 位置,不可继续读。

整个过程可以用下图来理解,调用 flip() 方法以后,读出数据的同时 position 指针不断往后挪动,到达 limit 指针的位置时,该次读取操作结束。

介绍完缓冲区后,我们知道它是存储数据的空间,进程可以将缓冲区中的数据读取出来,也可以写入新的数据到缓冲区,那缓冲区的数据从哪里来,又怎么写出去呢?接下来我们需要学习传输数据的介质:通道(Channel)

通道(Channel)

上面我们介绍过,通道是作为一种连接资源,作用是传输数据,而真正存储数据的是缓冲区,所以介绍完缓冲区后,我们来学习通道这一块。

通道是可以双向读写的,传统的 BIO 需要使用输入/输出流表示数据的流向,在 NIO 中可以减少通道资源的消耗。

通道类都保存在 java.nio.channels 包下,我们日常用到的几个重要的类有 4 个:

IO 通道类型 具体类
文件 IO FileChannel(用于文件读写、操作文件的通道)
TCP 网络 IO SocketChannel(用于读写数据的 TCP 通道)、ServerSocketChannel(监听客户端的连接)
UDP 网络 IO DatagramChannel(收发 UDP 数据报的通道)

可以通过 getChannel() 方法获取一个通道,支持获取通道的类如下:

  • 文件 IO:FileInputStream、FileOutputStream、RandomAccessFile
  • TCP 网络 IO:Socket、ServerSocket
  • UDP 网络 IO:DatagramSocket

示例:文件拷贝案例

我们来看一个利用通道拷贝文件的例子,需要下面几个步骤:

  • 打开原文件的输入流通道,将字节数据读入到缓冲区中
  • 打开目的文件的输出流通道,将缓冲区中的数据写到目的地
  • 关闭所有流和通道(重要!)

这是一张小菠萝的照片,它存在于d:\小菠萝\文件夹下,我们将它拷贝到 d:\小菠萝分身\ 文件夹下。

public class Test {/** 缓冲区的大小 */public static final int SIZE = 1024;public static void main(String[] args) throws IOException {// 打开文件输入流FileChannel inChannel = new FileInputStream("d:\小菠萝\小菠萝.jpg").getChannel();// 打开文件输出流FileChannel outChannel = new FileOutputStream("d:\小菠萝分身\小菠萝-拷贝.jpg").getChannel();// 分配 1024 个字节大小的缓冲区ByteBuffer dsts = ByteBuffer.allocate(SIZE);// 将数据从通道读入缓冲区while (inChannel.read(dsts) != -1) {// 切换缓冲区的读写模式dsts.flip();// 将缓冲区的数据通过通道写到目的地outChannel.write(dsts);// 清空缓冲区,准备下一次读dsts.clear();}inChannel.close();outChannel.close();}}

我画了一张图帮助你理解上面的这一个过程。

有人会问,NIO 的文件拷贝和传统 IO 流的文件拷贝有何不同呢?我们在编程时感觉它们没有什么区别呀,貌似只是 API 不同罢了,我们接下来就去看看这两者之间的区别吧。

BIO 和 NIO 拷贝文件的区别

这个时候就要来了解了解操作系统底层是怎么对 IO 和 NIO 进行区别的,我会用尽量通俗的文字带你理解,可能并不是那么严谨。

操作系统最重要的就是内核,它既可以访问受保护的内存,也可以访问底层硬件设备,所以为了保护内核的安全,操作系统将底层的虚拟空间分为了用户空间内核空间,其中用户空间就是给用户进程使用的,内核空间就是专门给操作系统底层去使用的。

接下来,有一个 Java 进程希望把小菠萝这张图片从磁盘上拷贝,那么内核空间和用户空间都会有一个缓冲区

  • 这张照片就会从磁盘中读出到内核缓冲区中保存,然后操作系统将内核缓冲区中的这张图片字节数据拷贝到用户进程的缓冲区中保存下来,对应着下面这幅图

  • 然后用户进程会希望把缓冲区中的字节数据写到磁盘上的另外一个地方,会将数据拷贝到 Socket 缓冲区中,最终操作系统再将 Socket 缓冲区的数据写到磁盘的指定位置上。

这一轮操作下来,我们数数经过了几次数据的拷贝?4 次。有 2 次是内核空间和用户空间之间的数据拷贝,这两次拷贝涉及到用户态和内核态的切换,需要CPU参与进来,进行上下文切换。而另外 2 次是硬盘和内核空间之间的数据拷贝,这个过程利用到 DMA与系统内存交换数据,不需要 CPU 的参与。

导致 IO 性能瓶颈的原因:内核空间与用户空间之间数据过多无意义的拷贝,以及多次上下文切换

操作 状态
用户进程请求读取数据 用户态 -> 内核态
操作系统内核返回数据给用户进程 内核态 -> 用户态
用户进程请求写数据到硬盘 用户态 -> 内核态
操作系统返回操作结果给用户进程 内核态 -> 用户态

在用户空间与内核空间之间的操作,会涉及到上下文的切换,这里需要 CPU 的干预,而数据在两个空间之间来回拷贝,也需要 CPU 的干预,这无疑会增大 CPU 的压力,NIO 是如何减轻 CPU 的压力?运用操作系统的零拷贝技术。

操作系统的零拷贝

所以,操作系统出现了一个全新的概念,解决了 IO 瓶颈:零拷贝。零拷贝指的是内核空间与用户空间之间的零次拷贝

零拷贝可以说是 IO 的一大救星,操作系统底层有许多种零拷贝机制,我这里仅针对 Java NIO 中使用到的其中一种零拷贝机制展开讲解。

在 Java NIO 中,零拷贝是通过用户空间和内核空间的缓冲区共享一块物理内存实现的,也就是说上面的图可以演变成这个样子。

image-20200904122132978
这时,无论是用户空间还是内核空间操作自己的缓冲区,本质上都是操作这一块共享内存中的缓冲区数据,省去了用户空间和内核空间之间的数据拷贝操作

现在我们重新来拷贝文件,就会变成下面这个步骤:

  • 用户进程通过系统调用 read() 请求读取文件到用户空间缓冲区(第一次上下文切换),用户态 -> 核心态,数据从硬盘读取到内核空间缓冲区中(第一次数据拷贝
  • 系统调用返回到用户进程(第二次上下文切换),此时用户空间与内核空间共享这一块内存(缓冲区),所以不需要从内核缓冲区拷贝到用户缓冲区
  • 用户进程发出 write() 系统调用请求写数据到硬盘上(第三次上下文切换),此时需要将内核空间缓冲区中的数据拷贝到内核的 Socket 缓冲区中(第二次数据拷贝
  • 由 DMA 将 Socket 缓冲区的内容写到硬盘上(第三次数据拷贝),write() 系统调用返回(第四次上下文切换

整个过程就如下面这幅图所示。

图中,需要 CPU 参与工作的步骤只有第③个步骤,对比于传统的 IO,CPU 需要在用户空间与内核空间之间参与拷贝工作,需要无意义地占用 2 次 CPU 资源,导致 CPU 资源的浪费。

下面总结一下操作系统中零拷贝的优点:

  • 降低 CPU 的压力:避免 CPU 需要参与内核空间与用户空间之间的数据拷贝工作
  • 减少不必要的拷贝:避免用户空间与内核空间之间需要进行数据拷贝

上面的图示可能并不严谨,对于你理解零拷贝会有一定的帮助,关于零拷贝的知识点可以去查阅更多资料哦,这是一门大学问。

介绍完通道后,我们知道它是用于传输数据的一种介质,而且是可以双向读写的,那么如果放在网络 IO 中,这些通道如果有数据就绪时,服务器是如何发现并处理的呢?接下来我们去学习 NIO 中的最后一个重要知识点:选择器(Selector)

选择器(Selectors)

选择器是提升 IO 性能的灵魂之一,它底层利用了多路复用 IO机制,让选择器可以监听多个 IO 连接,根据 IO 的状态响应到服务器端进行处理。通俗地说:选择器可以监听多个 IO 连接,而传统的 BIO 每个 IO 连接都需要有一个线程去监听和处理。

图中很明显的显示了在 BIO 中,每个 Socket 都需要有一个专门的线程去处理每个请求,而在 NIO 中,只需要一个 Selector 即可监听各个 Socket 请求,而且 Selector 并不是阻塞的,所以不会因为多个线程之间切换导致上下文切换带来的开销

image-20200904185402331

在 Java NIO 中,选择器是使用 Selector 类表示,Selector 可以接收各种 IO 连接,在 IO 状态准备就绪时,会通知该通道注册的 Selector,Selector 在下一次轮询时会发现该 IO 连接就绪,进而处理该连接。

Selector 选择器主要用于网络 IO当中,在这里我会将传统的 BIO Socket 编程和使用 NIO 后的 Socket 编程作对比,分析 NIO 为何更受欢迎。首先先来了解 Selector 的基本结构。

重要方法 方法解析
open() 打开一个 Selector 选择器
int select() 阻塞地等待就绪的通道
int select(long timeout) 最多阻塞 timeout 毫秒,如果是 0 则一直阻塞等待,如果是 1 则代表最多阻塞 1 毫秒
int selectNow() 非阻塞地轮询就绪的通道

在这里,你会看到 select() 和它的重载方法是会阻塞的,如果用户进程轮询时发现没有就绪的通道,操作系统有两种做法:

  • 一直等待直到一个就绪的通道,再返回给用户进程
  • 立即返回一个错误状态码给用户进程,让用户进程继续运行,不会阻塞

这两种方法对应了同步阻塞 IO同步非阻塞 IO ,这里读者的一点小的观点,请各位大神批判阅读

Java 中的 NIO 不能真正意义上称为 Non-Blocking IO,我们通过 API 的调用可以发现,select() 方法还是会存在阻塞的现象,根据传入的参数不同,操作系统的行为也会有所不同,不同之处就是阻塞还是非阻塞,所以我更倾向于把 NIO 称为 New IO,因为它不仅提供了 Non-Blocking IO,而且保留原有的 Blocking IO 的功能。

了解了选择器之后,它的作用就是:监听多个 IO 通道,当有通道就绪时选择器会轮询发现该通道,并做相应的处理。那么 IO 状态分为很多种,我们如何去识别就绪的通道是处于哪种状态呢?在 Java 中提供了选择键(SelectionKey)

选择键(SelectionKey)

在 Java 中提供了 4 种选择键:

  • SelectionKey.OP_READ:套接字通道准备好进行读操作
  • SelectionKey.OP_WRITE:套接字通道准备好进行写操作
  • SelectionKey.OP_ACCEPT:服务器套接字通道接受其它通道
  • SelectionKey.OP_CONNECT:套接字通道准备完成连接

在 SelectionKey 中包含了许多属性

  • channel:该选择键绑定的通道
  • selector:轮询到该选择键的选择器
  • readyOps:当前就绪选择键的值
  • interesOps:该选择器对该通道感兴趣的所有选择键

选择键的作用是:在选择器轮询到有就绪通道时,会返回这些通道的就绪选择键(SelectionKey),通过选择键可以获取到通道进行操作。

简单了解了选择器后,我们可以结合缓冲区、通道和选择器来完成一个简易的聊天室应用。

示例:简易的客户端服务器通信

先说明,这里的代码非常的臭和长,不推荐细看,直接看注释附近的代码即可。

我们在服务器端会开辟两个线程

  • Thread1:专门监听客户端的连接,并把通道注册到客户端选择器上
  • Thread2:专门监听客户端的其它 IO 状态(读状态),当客户端的 IO 状态就绪时,该选择器会轮询发现,并作相应处理
public class NIOServer {Selector serverSelector = Selector.open();Selector clientSelector = Selector.open();public static void main(String[] args) throws IOException {NIOServer server = nwe NIOServer();new Thread(() -> {try {// 对应IO编程中服务端启动ServerSocketChannel listenerChannel = ServerSocketChannel.open();listenerChannel.socket().bind(new InetSocketAddress(3333));listenerChannel.configureBlocking(false);listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);server.acceptListener();} catch (IOException ignored) {}}).start();new Thread(() -> {try {server.clientListener();} catch (IOException ignored) {}}).start();}
}
// 监听客户端连接
public void acceptListener() {while (true) {if (serverSelector.select(1) > 0) {Set<SelectionKey> set = serverSelector.selectedKeys();Iterator<SelectionKey> keyIterator = set.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if (key.isAcceptable()) {try {// (1) 每来一个新连接,注册到clientSelectorSocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();clientChannel.configureBlocking(false);clientChannel.register(clientSelector, SelectionKey.OP_READ);} finally {// 从就绪的列表中移除这个keykeyIterator.remove();}}}}}
}
// 监听客户端的 IO 状态就绪
public void clientListener() {while (true) {// 批量轮询是否有哪些连接有数据可读if (clientSelector.select(1) > 0) {Set<SelectionKey> set = clientSelector.selectedKeys();Iterator<SelectionKey> keyIterator = set.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();// 判断该通道是否读就绪状态if (key.isReadable()) {try {// 获取客户端通道读入数据SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(1024);clientChannel.read(byteBuffer);byteBuffer.flip();System.out.println(LocalDateTime.now().toString() + " Server 端接收到来自 Client 端的消息: " +Charset.defaultCharset().decode(byteBuffer).toString());} finally {// 从就绪的列表中移除这个keykeyIterator.remove();key.interestOps(SelectionKey.OP_READ);}}}}}
}

在客户端,我们可以简单的输入一些文字,发送给服务器

public class NIOClient {public static final int CAPACITY = 1024;public static void main(String[] args) throws Exception {ByteBuffer dsts = ByteBuffer.allocate(CAPACITY);SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 3333));socketChannel.configureBlocking(false);Scanner sc = new Scanner(System.in);while (true) {String msg = sc.next();dsts.put(msg.getBytes());dsts.flip();socketChannel.write(dsts);dsts.clear();}}}

下图可以看见,在客户端给服务器端发送信息,服务器接收到消息后,可以将该条消息分发给其它客户端,就可以实现一个简单的群聊系统,我们还可以给这些客户端贴上标签例如用户姓名,聊天等级······,就可以标识每个客户端啦。在这里由于篇幅原因,我没有写出所有功能,因为使用原生的 NIO 实在是不太便捷。

我相信你们都是直接滑下来看这里的,我在写这段代码的时候也非常痛苦,甚至有点厌烦 Java 原生的 NIO 编程。实际上我们在日常开发中很少直接用 NIO 进行编程,通常都会用 Netty,Mina 这种服务器框架,它们都是很好地 NIO 技术,对 Java 原生的 NIO 进行了上层的封装、优化,简化开发难度,但是在学习框架之前,我们需要了解它底层原生的技术,就像 Spring AOP 的动态代理,Spring IOC 容器的 Map 容器存储对象,Netty 底层的 NIO 基础······

总结

NIO 的三大板块基本上都介绍完了,我没有做过多详细的 API 介绍,我希望能够通过这篇文章让你们对以下内容有所认知

  • Java IO 体系的组成部分:BIO 和 NIO
  • BIO 的基本组成部分:字节流,字符流,转换流和处理流
  • NIO 的三大重要模块:缓冲区(Buffer),通道(Channel),选择器(Selector)以及它们的作用
  • NIO 与 BIO 两者的对比:同步/非同步、阻塞/非阻塞,在文件 IO 和 网络 IO 中,使用 NIO 相对于使用 BIO 有什么优势
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. java实现快速排序(附代码解析)

    public void quickSort(int[] arr, int low, int high) {if (low > high) return;//确认左指针int i=low;//确认右指针int j=high;//确定校验位 一般是第一个int temp=arr[low];//交换元素的工具int t;//开始循环while (i<j){while (i<j&&arr[i]<temp)i++;…...

    2024/4/28 17:58:28
  2. 分享一个单片机在工业传感器应用中常用的PWM转2-20mA输出电路

    分享一个单片机在工业传感器应用中常用的PWM转2-20mA输出电路 电路经本人亲测有效 电路是我设计一款温湿度传感器的时候不断调试过的 下图是电源原理图:附件PDF是全部电路原理图,希望对大家设计有帮助。...

    2024/4/28 16:51:49
  3. 友元 异常

    一、友元在前文中,我们将一些友元函数用于类的扩展接口,而类并非只能拥有友元函数,同时,也可以将类作为友元。在这种情况下,友元类的所有方法都能访问原始类的私有成员和保护成员。也可将特定的成员函数指定为另一个类的友元。1、友元类友元类的声明应该类似如下:friend …...

    2024/4/27 21:28:24
  4. springboot工程导入本地jar

    首先,你需要一个springboot工程在主目录({basedir})下新建文件夹(lib),放入你的jar包(aspose-cad-19.9.jar)接下来,打开pom.xml文件,添加下列代码 此处需要注意的是,你需要知道这个jar包的groupId、artifactId、version诶!可以直接复制哦!<dependency><g…...

    2024/4/28 20:14:08
  5. C++STL

    STL是什么 C++标准:ANSI是ISO组织的美国分支机构。 所以,他们是一样的。 通常是 ANSI制定标准,然后ISO修改批准,ANSI再修改,所以最后他们就一样了,一般写作ANSI/ISO C++。 翻译过来就是标准C++。 STL是C++的ANSI/ISO标准的一部分,STL提供了大量的可服用软件组织, STL…...

    2024/4/28 19:21:25
  6. 160. 相交链表

    编写一个程序,找到两个单链表相交的起始节点。 如下面的两个链表: 在节点 c1 开始相交。 示例 1: 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 输出:Reference of the node with value = 8 输入解释:相交节点的值为 8 (注…...

    2024/4/28 16:35:58
  7. 批量上架!!!!

    批量上架 商品!!批量上架商品!!!看看流程步骤! 1、复制京东链接 2、归类好 京东链接,都是一个系列的产品 加微信了解 : maomaoOS...

    2024/4/26 22:47:00
  8. BitMap的原理和实现

    相关概念基础类型在java中:byte -> 8 bits -->1字节 char -> 16 bit -->2字节 short -> 16 bits -->2字节 int -> 32 bits -->4字节 float -> 32 bits -->4字节 long -> 64 bits -->8字节位运算符在java中,int数据底…...

    2024/4/28 15:52:04
  9. C++ LeetCode104 二叉树的最大深度

    递归比较 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/ class Solution { public:int maxDepth(TreeNode* root) {if(root==NULL…...

    2024/4/28 13:05:08
  10. 关于映入aar包的方法

    突然最近发现很多SDK的引入由原来的jar包形式更换成aar形式,比方说最新版本的友盟crash的包,支付宝最新支付包。然后我们项目采用模块化开发,就造成尝试以前的各种方法引入不进来。关键是以前也很少有lib用aar的形式.先说说我在项目导入aar包的方法,然后再说一个IT大飞说写…...

    2024/4/28 12:19:55
  11. 6.纹理

    1.纹理采样:根据纹理坐标获取像素值2.纹理环绕:当纹理坐标超出默认范围时纹理的重复方式,OpenGL提供了以下几种方式对纹理环绕方式进行设置:glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, G…...

    2024/4/27 23:12:45
  12. configure.ac:24: error: must install xorg-macros 1.15 or later before running autoconf/autogen

    使用apt-get install xutils-dev 转自:configure.ac:24: error: must install xorg-macros 1.15 or later before running autoconf/autogen...

    2024/4/28 0:53:29
  13. Eureka服务器搭建入门

    1、加入依赖<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>2、application.yml配置 server:port: 10086 spring:application:name: eureka…...

    2024/4/27 13:19:46
  14. mybatis源码学习------缓存模块

    简介 在优化系统性能时,优化数据库性能是非常重要的一个环节,而添加缓存则是优化数据库时最有效的手段之一。正确、合理地使用缓存可以将一部分数据库请求拦截在缓存这一层。 MyBatis 中提供了一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓存模块实现的。这里…...

    2024/4/27 6:54:31
  15. 基于关联规则与可平面图的商品摆放规划-----实验报告

    基于关联规则与可平面图的商品摆放规划 摘要: 本文先对northwind数据库介绍与数据描述与简单分析(数据异常值处理,订单地址的文本挖掘),然后对购买的商品使用关联规则算法,进行关联分析与商品的购买情况分析,由关联规则的发现结果,使用图论方法分析商品的摆放图。 关键词: …...

    2024/4/25 5:07:41
  16. Linux编译安装中configure、make和make install各自的作用详解

    前言:我们经常在Linux中通过源码安装软件会经过以下三个步骤,但是每一个步骤到底是什么意思呢?本文来详细说明。总的来说,这些都是典型的使用GNU的Autoconfigure和Automake产生的程序的安装步骤。(1)./configure是用来检测你的安装平台的目标特征的。比如它会检测你是不是…...

    2024/4/11 11:29:53
  17. XSS攻击原理及防御措施

    一、XSS攻击原理 XSS是什么?它的全名是:Cross-site scripting,为了和CSS层叠样式表区分所以取名XSS。是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML以及用户端脚本语言…...

    2024/4/27 2:59:05
  18. go语言学习笔记(二)

    go程序函数可变参数函数匿名函数方法接口语句 函数 函数名(参数名 参数类型,参数名 参数类型) 返回值类型 { } 可以没有函数中的输入参数和返回值func 函数名() { } 如果一个函数有多个返回值,那么这些返回值必须用 ( ,) 括起来。 从函数中可以返回一个命名值。一旦命名了返…...

    2024/4/28 8:22:27
  19. 如何搭建分销国外网店系统呢

    许多人利用身旁小伙伴推荐或刷朋友圈的那时候发觉分销网店,许多店家都看中分销国外网店系统的发展前途。殊不知,如何搭建分销国外网店系统变成了许多 店家的拦路虎,今日beeecshop小编就来协助店家处理这个拦路虎的状况。 第一步:为分销国外网店取1个简易且短的域名 要创建1…...

    2024/4/27 2:18:41
  20. 为何国外网店系统开发如此火

    伴随着科技进步的持续创新,网站建设技术应用和系统功能也在不停的提高。现如今电商网站的作用变得越来越强劲,电商网店的网页也设计构思得变得越来越炫,以便提高企业的市场占有率和提高商品的名气,许多 商户也为自己的企业研发了自身的外贸电商系统。有着外贸电商国外网店系…...

    2024/4/1 5:48:04

最新文章

  1. 【面经】汇总

    面经 Java基础集合都有哪些面向对象的三大特点ArrayList和LinkedList的区别&#xff1f;ArrayList底层扩容是怎么实现的&#xff1f;讲一讲HashMap、以及put方法的过程讲一讲HashMap的扩容过程Hashmap为什么要用红黑树而不用其他的树&#xff1f;Java8新特性有哪些LoadFactor负…...

    2024/4/28 20:19:45
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 自我介绍的HTML 页面(入门)

    一.前情提要 1.主要是代码示例&#xff0c;具体内容需自己填充 2.代码后是详解 二.代码实例和解析 代码 <!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"UTF-8"> <title>自我介绍页面</title>…...

    2024/4/26 14:55:53
  4. 字符串匹配算法之BF与KMP算法

    目录 BF算法(暴力匹配算法) KMP算法 核心思想&#xff1a; next数组 next数组的优化 BF算法(暴力匹配算法) #include <assert.h> int BF(const char* str, const char* sub) {assert(str ! NULL && sub ! NULL);if (str NULL || sub NULL){return -1;}int…...

    2024/4/26 13:54:05
  5. 416. 分割等和子集问题(动态规划)

    题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义&#xff1a;dp[i][j]表示当背包容量为j&#xff0c;用前i个物品是否正好可以将背包填满&#xff…...

    2024/4/28 4:04:40
  6. 【Java】ExcelWriter自适应宽度工具类(支持中文)

    工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...

    2024/4/28 12:01:04
  7. Spring cloud负载均衡@LoadBalanced LoadBalancerClient

    LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon&#xff0c;直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件&#xff0c;我们讨论Spring负载均衡以Spring Cloud2020之后版本为主&#xff0c;学习Spring Cloud LoadBalance&#xff0c;暂不讨论Ribbon…...

    2024/4/28 16:34:55
  8. TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案

    一、背景需求分析 在工业产业园、化工园或生产制造园区中&#xff0c;周界防范意义重大&#xff0c;对园区的安全起到重要的作用。常规的安防方式是采用人员巡查&#xff0c;人力投入成本大而且效率低。周界一旦被破坏或入侵&#xff0c;会影响园区人员和资产安全&#xff0c;…...

    2024/4/28 18:31:47
  9. VB.net WebBrowser网页元素抓取分析方法

    在用WebBrowser编程实现网页操作自动化时&#xff0c;常要分析网页Html&#xff0c;例如网页在加载数据时&#xff0c;常会显示“系统处理中&#xff0c;请稍候..”&#xff0c;我们需要在数据加载完成后才能继续下一步操作&#xff0c;如何抓取这个信息的网页html元素变化&…...

    2024/4/28 12:01:03
  10. 【Objective-C】Objective-C汇总

    方法定义 参考&#xff1a;https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...

    2024/4/28 12:01:03
  11. 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】

    &#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格…...

    2024/4/28 12:01:03
  12. 【ES6.0】- 扩展运算符(...)

    【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数&#xff0…...

    2024/4/28 16:07:14
  13. 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?

    文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕&#xff0c;各大品牌纷纷晒出优异的成绩单&#xff0c;摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称&#xff0c;在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁&#xff0c;多个平台数据都表现出极度异常…...

    2024/4/27 21:08:20
  14. Go语言常用命令详解(二)

    文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命令可以帮助您在Go开发中进行编译、测试、运行和…...

    2024/4/28 9:00:42
  15. 用欧拉路径判断图同构推出reverse合法性:1116T4

    http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b&#xff0c;我们在 a i a_i ai​ 和 a i 1 a_{i1} ai1​ 之间连边&#xff0c; b b b 同理&#xff0c;则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然&#xff0…...

    2024/4/27 18:40:35
  16. 【NGINX--1】基础知识

    1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息&#xff0c;并安装一些有助于配置官方 NGINX 软件包仓库的软件包&#xff1a; apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...

    2024/4/28 4:14:21
  17. Hive默认分割符、存储格式与数据压缩

    目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限&#xff08;ROW FORMAT&#xff09;配置标准HQL为&#xff1a; ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...

    2024/4/27 13:52:15
  18. 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法

    文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中&#xff0c;传感器和控制器产生大量周…...

    2024/4/27 13:38:13
  19. --max-old-space-size=8192报错

    vue项目运行时&#xff0c;如果经常运行慢&#xff0c;崩溃停止服务&#xff0c;报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中&#xff0c;通过JavaScript使用内存时只能使用部分内存&#xff08;64位系统&…...

    2024/4/28 12:00:58
  20. 基于深度学习的恶意软件检测

    恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞&#xff0c;例如可以被劫持的合法软件&#xff08;例如浏览器或 Web 应用程序插件&#xff09;中的错误。 恶意软件渗透可能会造成灾难性的后果&#xff0c;包括数据被盗、勒索或网…...

    2024/4/28 12:00:58
  21. JS原型对象prototype

    让我简单的为大家介绍一下原型对象prototype吧&#xff01; 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定&#xff0c;每一个构造函数都有一个 prototype 属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象…...

    2024/4/27 22:51:49
  22. C++中只能有一个实例的单例类

    C中只能有一个实例的单例类 前面讨论的 President 类很不错&#xff0c;但存在一个缺陷&#xff1a;无法禁止通过实例化多个对象来创建多名总统&#xff1a; President One, Two, Three; 由于复制构造函数是私有的&#xff0c;其中每个对象都是不可复制的&#xff0c;但您的目…...

    2024/4/28 7:31:46
  23. python django 小程序图书借阅源码

    开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…...

    2024/4/28 8:32:05
  24. 电子学会C/C++编程等级考试2022年03月(一级)真题解析

    C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...

    2024/4/27 20:28:35
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57