Java IO学习和总结(超详细)

news2024/11/18 21:36:49

一、理解

        I/O 是输入和输出的简写,指的是数据在计算机内部和外部设备之间的流动。简单来说,当你从键盘输入数据、从鼠标选择操作,或者在屏幕上看到图像,这些都是 I/O 操作。它就像是计算机与外部世界沟通的桥梁,没有 I/O,计算机就不知道如何接收和发送信息。

       当然,涉及I/O操作,不仅是硬件设备的读写I/O(磁盘I/O),而且还有网络的I/O(网络I/O)。总结来说I/O是处理人机交互、机与机交互的一套解决方案。

二、Java IO 分类

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

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

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

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

       字节IO和字符IO主要是从传输数据的数据格式不同进行分组,而后两个磁盘和网络IO则是从传输数据的方式不同进行分组。数据格式和传输方式是影响效率的最关键因素。

三、基于字节操作的接口

3.1 字节输入流( InputStream)

      InputStream 输入流的类继承层次如下图所示:

     输入流根据数据节点类型和处理方式,分别可以划分出了若干个子类,如下图:

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

3.2 字节输出流(OutputStream)

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

       输出流根据数据节点类型和处理方式,也分别可以划分出了若干个子类,如下图:

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

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

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

四、基于字符操作的接口

        不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作的都是字节而不是字符,但是为什么要有操作字符的 I/O 接口呢?

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

4.1 字符输入流(Reader)

         基于字符的输入和输出操作接口分别是:Reader 和 Writer ,下图是字符的 I/O 操作接口涉及到的类结构图。

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

        同样的,输入流根据数据节点类型和处理方式,分别可以划分出了若干个子类,如下图:

4.2 字符输出流 (Writer)

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

        同样的,输出流根据数据节点类型和处理方式分类,分别可以划分出了若干个子类,如下图:

        不管是 Reader 还是 Writer 类,它们都只定义了读取或写入数据字符的方式,也就是说要么是读要么是写,但是并没有规定数据要写到哪去,写到哪去就是我们后面要讨论的基于磁盘或网络的工作机制。

五、字节与字符的转化

        刚刚我们说到,不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,设计字符的原因是为了程序操作更方便,那么怎么将字符转化成字节或者将字节转化成字符呢?

        InputStreamReader 和 OutputStreamWriter 就是转化桥梁。其中InputStreamReader将字节输入流转为字符输入流,OutputStreamWriter是将字符输出流转为字节输出流。

        众所周知,计算机中存储的数据都是二进制的数字,我们在电脑屏幕上看到的文字信息是将二进制转换之后显示的,两者之间存在编码解码的过程,其互相转换必须遵循某种规则,即编码和解码都遵循同一种规则才能将文字信息正常显示,如果编码跟解码使用了不同的规则,就会出现乱码的情况。

  • 编码:字符、字符串(能看懂的)--字节(看不懂的)
  • 解码:字节(看不懂的)-->字符、字符串(能看懂的) 

       上面说的编码与解码的过程需要遵循某种规则,这种规则就是不同的字符编码。我们在刚刚学习编程的时候最早接触就是ASCII码,它主要是用来显示英文和一些符号,到后面还有接触到别的编码规则常用的有:gb2312,gbk,utf-8等。它们分别属于不同的编码集。

         我们需要明确的是字符编码和字符集是两个不同层面的概念。

  • encoding是charset encoding的简写,即字符集编码,简称编码
  • charset是character set的简写,即字符集

      编码是依赖于字符集的,一个字符集可以有多个编码实现,就像代码中的接口实现依赖于接口一样。

        转换流的原理是:字符流 = 字节流 + 编码表。在转换流中选择正确的编码非常的重要,因为指定了编码,它所对应的字符集自然就指定了,否则很容易出现乱码,所以编码才是我们最终要关心的。    

转换流的特点:其是字符流和字节流之间的桥梁。

  1.        可对读取到的字节数据经过指定编码转换成字符
  2.        可对读取到的字符数据经过指定编码转换成字节

那么何时使用转换流?

  1.        当字节和字符之间有转换动作时
  2.        流操作的数据需要编码或解码时

5.1、输入流转化过程

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

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

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

打开源码部分,InputStream 到 Reader 转化过程,如下图:

1、InputStreamReader

         InputStreamReader是字节流到字符流的桥梁:它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

  • InputStreamReader(InputStream in):创建一个默认字符集字符输入流。
  • InputStreamReader(InputStream in, String charsetName):创建一个指定字符集的字符流。
构造方法示例代码:

    InputStreamReader isr1 = new InputStreamReader(new FileInputStream("D:\\IO\\utf8.txt"));
    InputStreamReader isr2 = new InputStreamReader(new FileInputStream("D:\\IO\\utf8.txt"),"UTF-8");

构造方法:

InputStreamReader ins = new InputStreamReader(new FileInputStream(“test.txt”));

 5.2、输出流转化过程

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

 

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

源码部分,Writer 到 OutputStream 转化过程,如下图:

2、OutputStreamWriter转化举例

OutputStreamWriter是字符流通向字节流的桥梁:用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法:

  • OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
  • OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。

构造方法示例代码:

  OutputStreamWriter isr1 = new OutputStreamWriter(new FileOutputStream("D:\\IO\\gbk.txt""));
    OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("D:\\IO\\gbk1.txt") , "GBK");

六、基于磁盘操作的接口

前面介绍了 Java I/O 的操作接口,这些接口主要定义了如何操作数据,以及介绍了操作数据格式的方式:字节流和字符流。

还有一个关键问题就是数据写到何处,其中一个主要的处理方式就是将数据持久化到物理磁盘。

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

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

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

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

6.1 File类的使用

1、File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
它可能是最常用的创建File对象的方式:

File file = new File("E://test//Test.txt");

2、如果电脑的E盘中真实存在test文件夹且里面有名为Test.txt的相应文件,那么该file对象就会和硬盘中对应真实存在的Test.txt文件产生一一映射关系。
如果这个文件在硬盘上不存在也没关系,这个对象依然存在,只是没有了映射关系,它所有属性都是默认值。
如果给的路径是一个相对路径, 那么它是相对于当前项目所在硬盘上的目录。

File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。
File parent = new File("E://test");
File file = new File(parent,"Test.txt");
File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。
File file = new File("E:\\test","Test.txt");

其实//和\\都可以表示代码中文件层级的分隔符

File(URI uri) :通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。

6.2 File类的一些主要方法

String path = getAbsolutePath() //获取文件的绝对路径
String name = getName() //获取文件的名字
boolean = createNewFile() //创建新的文件,可能会发生编译时异常:IOException
boolean = mkdir() //创建新的文件夹 如果父目录不存在,则不能创建
boolean = mkdirs() //创建新的文件夹 即使父目录不存在,也可以创建,即创建级联文件目录
File file = getParentFile() //获取当前file的父亲file对象,如果没有父file对象则返回null
String pname = getParent()// 获取当前file的父亲file名字
File[] files = listFiles() //获取当前file的所有子对象,如果返回的数组对象值为null,则说明它是一个文件,如果不为null,则说明它是文件夹;如果返回的数组对象的length等于0,说明它是空文件夹,返回的数组的length即为它的子对象个数。
String[] names = list() //获取当前file的所有子对象名字
boolean = delete() //删除文件或空的文件夹,不能删除带元素的文件夹
long = length() //获取文件中字节的个数
long = lastModified()//获取文件最后的修改时间—>毫秒值
 File file = new File("E:\\test","Test.txt");
 long length = file.lastModified();
 Date date = new Date(length);
 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss");
 String time = sdf.format(date);
 System.out.println(time);//2023-04-21 08:46:47

七、基于网络操作的接口

继续来说说数据写到何处的另一种处理方式:将数据写入互联网中以供其他电脑能访问

7.1、Socket 简介

在现实中,Socket 这个概念没有一个具体的实体,它是描述计算机之间完成相互通信一种抽象定义。

打个比方,可以把 Socket 比作为两个城市之间的交通工具,有了它,就可以在城市之间来回穿梭了。并且,交通工具有多种,每种交通工具也有相应的交通规则。Socket 也一样,也有多种。大部分情况下我们使用的都是基于 TCP/IP 的流套接字,它是一种稳定的通信协议。

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

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

7.2、建立通信链路

我们知道网络层使用的 IP 协议可以帮助我们根据 IP 地址来找到目标主机,但是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通信就要通过 TCP 或 UPD 的地址也就是端口号来指定。这样就可以通过一个 Socket 实例代表唯一一个主机上的一个应用程序的通信链路了。

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

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

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

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

这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。

简单流程如下:

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

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

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

完成三次握手之后,客户端应用程序与服务器应用程序就可以开始传送数据了。

传输数据是我们建立连接的主要目的,如何通过 Socket 传输数据呢?

7.3、socket客户端和服务端实现

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

 与之对应的服务端,也将创建一个 ServerSocket 实例,ServerSocket 创建比较简单,只要指定的端口号没有被占用,一般实例创建都会成功,同时操作系统也会为 ServerSocket 实例创建一个底层数据结构,这个数据结构中包含指定监听的端口号和包含监听地址的通配符,通常情况下都是*即监听所有地址。

之后当调用 accept() 方法时,将进入阻塞状态,等待客户端的请求。

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

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

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

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

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

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

如何提高网络 IO 传输效率、保证数据传输的可靠,已经成了工程师们急需解决的问题。

7.4、IO 工作方式概述(BIO/NIO/AIO

在计算机中,IO 传输数据有三种工作方式,分别是 BIO、NIO、AIO

在讲解 BIO、NIO、AIO 之前,我们先来回顾一下这几个概念:同步与异步,阻塞与非阻塞

同步与异步的区别

  • 同步就是发起一个请求后,接受者未处理完请求之前,不返回结果。

  • 异步就是发起一个请求后,立刻得到接受者的回应表示已接收到请求,但是接受者并没有处理完,接受者通常依靠事件回调等机制来通知请求者其处理结果。

阻塞和非阻塞的区别

  • 阻塞就是请求者发起一个请求,一直等待其请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。

  • 非阻塞就是请求者发起一个请求,不用一直等着结果返回,可以先去干其他事情,当条件就绪的时候,就自动回来。

而我们要讲的 BIO、NIO、AIO 就是同步与异步、阻塞与非阻塞的组合。

  • BIO:同步阻塞 IO;

  • NIO:同步非阻塞 IO;

  • AIO:异步非阻塞 IO;

7.5 BIO

BIO 俗称同步阻塞 IO,一种非常传统的 IO 模型,一个客户端一个线程。比如我们上面所举的那个程序例子,就是一个典型的**同步阻塞 IO **的工作方式。

采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。

我们一般在服务端通过while(true)循环中会调用accept()方法等待监听客户端的连接,一旦接收到一个连接请求,就可以建立通信套接字进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接。

客户端多线程操作,程序如下:

 服务端多线程操作,程序如下:

服务端运行结果,如下: 

 如果要让 BIO 通信模型能够同时处理多个客户端请求,就必须使用多线程,也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。

这就是典型的一请求一应答通信模型 。

如果出现 100、1000、甚至 10000 个用户同时访问服务器,这个时候,如果使用这种模型,那么服务端也会创建与之相同的线程数量,线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务

当然,我们可以通过使用 Java 中 ThreadPoolExecutor 线程池机制来改善,让线程的创建和回收成本相对较低,保证了系统有限的资源的控制,实现了 N (客户端请求数量)大于 M (处理客户端请求的线程数量)的伪异步 I/O 模型。

7.6 伪异步 BIO

为了解决同步阻塞 I/O 面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数 M:线程池最大线程数 N 的比例关系,其中 M 可以远远大于 N,通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致资源耗尽。

伪异步 IO 模型图,如下图:

 采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,当有新的客户端接入时,将客户端的 Socket 封装成一个 Task 投递到后端的线程池中进行处理。

Java 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。

客户端,程序如下:

服务端,程序如下:

先启动服务端程序,再启动客户端程序,看看运行结果!

 服务端,运行结果如下:

客户端,运行结果如下:

 

本例中测试的客户端数量是 30,服务端使用 java 线程池来处理任务,线程数量为 5 个,服务端不用为每个客户端都创建一个线程,由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

在活动连接数不是特别高的情况下,这种模型是还不错,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。

但是,它的底层仍然是同步阻塞的 BIO 模型,当面对十万甚至百万级连接的时候,传统的 BIO 模型真的是无能为力的,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

7.7  NIO

NIO 中的 N 可以理解为 Non-blocking,一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入,对应的在java.nio包下。

NIO 新增了 Channel、Selector、Buffer 等抽象概念,支持面向缓冲、基于通道的 I/O 操作方法。

NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。

NIO 这两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反

对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发效率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。

我们先看一下 NIO 涉及到的核心关联类图,如下:

 

上图中有三个关键类:Channel 、Selector 和 Buffer,它们是 NIO 中的核心概念。

  • Channel:可以理解为通道;

  • Selector:可以理解为选择器;

  • Buffer:可以理解为数据缓冲流;

我们还是用前面的城市交通工具来继续形容 NIO 的工作方式,这里的 Channel 要比 Socket 更加具体,它可以比作为某种具体的交通工具,如汽车或是高铁、飞机等,而 Selector 可以比作为一个车站的车辆运行调度系统,它将负责监控每辆车的当前运行状态:是已经出站还是在路上等等,也就是说它可以轮询每个 Channel 的状态。

还有一个 Buffer 类,你可以将它看作为 IO 中 Stream,但是它比 IO 中的 Stream 更加具体化,我们可以将它比作为车上的座位,Channel 如果是汽车的话,那么 Buffer 就是汽车上的座位,Channel 如果是高铁上,那么 Buffer 就是高铁上的座位,它始终是一个具体的概念,这一点与 Stream 不同。

Socket 中的 Stream 只能代表是一个座位,至于是什么座位由你自己去想象,也就是说你在上车之前并不知道这个车上是否还有没有座位,也不知道上的是什么车,因为你并不能选择,这些信息都已经被封装在了运输工具(Socket)里面了。

NIO 引入了 Channel、Buffer 和 Selector 就是想把 IO 传输过程中涉及到的信息具体化,让程序员有机会去控制它们。

当我们进行传统的网络 IO 操作时,比如调用 write() 往 Socket 中的 SendQ 队列写数据时,当一次写的数据超过 SendQ 长度时,操作系统会按照 SendQ 的长度进行分割的,这个过程中需要将用户空间数据和内核地址空间进行切换,而这个切换不是程序员可以控制的,由底层操作系统来帮我们处理。

而在 Buffer 中,我们可以控制 Buffer 的 capacity(容量),并且是否扩容以及如何扩容都可以控制。

理解了这些概念后我们看一下,实际上它们是如何工作的呢?

还是以上面的操作为例子,为了方便观看结果,本次的客户端线程请求数改成 15 个。

客户端,程序如下:

服务端,程序如下: 

先启动服务端程序,再启动客户端程序,看看运行结果!

服务端,运行结果如下:

 客户端,运行结果如下:

当然,客户端也不仅仅只限制于 IO 的写法,还可以使用SocketChannel来操作客户端,程序如下:

 

 一样的,先启动服务端,再启动客户端,客户端运行结果如下:

从操作上可以看到,NIO 的操作比传统的 IO 操作要复杂的多!

Selector 被称为选择器 ,当然你也可以翻译为多路复用器 。它是 Java NIO 核心组件中的一个,用于检查一个或多个 Channel(通道)的状态是否处于连接就绪接受就绪可读就绪可写就绪

如此可以实现单线程管理多个 channels,也就是可以管理多个网络连接。

 

使用 Selector 的好处在于: 相比传统方式使用多个线程来管理 IO,Selector 使用了更少的线程就可以处理通道了,并且实现网络高效传输!

虽然 java 中的 nio 传输比较快,为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?

从上面的代码中大家都可以看出来,除了编程复杂、编程模型难之外,还有几个让人诟病的问题:

  • JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%!

  • 项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高!

但是,Google 的 Netty 框架的出现,很大程度上改善了 JDK 原生 NIO 所存在的一些让人难以忍受的问题。

 参考:

[0]如果有人再问你 Java IO,把这篇文章砸他头上-炸鸡可乐:https://mp.weixin.qq.com/s?__biz=MzkzODE3OTI0Ng==&mid=2247491115&idx=1&sn=14f1712cc787befd6c78d64612a00f95&chksm=c28571eaf5f2f8fca45ea7caf3ae7b5dee57c89e2cf7c0d115d0fa5211cca976e3462ea5cb74&token=950876690&lang=zh_CN&scene=21#wechat_redirect

[1]IBM - 许令波 -深入分析 Java I/O 的工作机制: https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html

[2]Github - JavaGuide -  IO总结: https://snailclimb.gitee.io/javaguide/#/

[3]博客园 - 五月的仓颉 -  IO和File: https://www.cnblogs.com/xrq730/p/4886636.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1371526.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Zookeeper设计理念与源码剖析

Zookeeper 架构理解 整体架构 Follower server 可以直接处理读请求,但不能直接处理写请求。写请求只能转发给 leader server 进行处理。最终所有的写请求在 leader server 端串行执行。(因为分布式环境下永远无法精确地确认不同服务器不同事件发生的先后…

【Flink精讲】Flink数据延迟处理

面试题:Flink数据延迟怎么处理? 将迟到数据直接丢弃【默认方案】将迟到数据收集起来另外处理(旁路输出)重新激活已经关闭的窗口并重新计算以修正结果(Lateness) Flink数据延迟处理方案 用一个案例说明三…

鸿蒙开发已解决-Failed to connect to gitee.com port 443: Time out 连接超时提示

文章目录 项目场景:问题描述原因分析:解决方案:解决方案1解决方案2:解决方案3:此Bug解决方案总结解决方案总结**心得体会:解决连接超时问题的三种方案**项目场景: 导入Sample时遇到导入失败的情况,并提示“Failed to connect to gitee.com port 443: Time out”连接超…

外汇天眼:MT5到底比MT4强在哪里?

这段时间,我们收到许多用户询问关于MT5比MT4优秀的地方,为解答多数投资人的疑惑,决定简短分享两者的差距。 其实MT4功能已经非常强大,但MT5的功能提升绝对不只一个量级,这也是工程师积极向MT5转型的主要原因&#xff0…

Apache ECharts | 一个数据可视化图表库

文章目录 1、简介1.1、主要特点1.2、使用场景 2、安装方式一:从下载的源代码或编译产物安装方法二:从 npm 安装方法三:⭐定制安装echarts.js 3、使用 官网: 英语:https://echarts.apache.org/en/index.html 中文&a…

使用Moonbuilders Academy平台,学习DApp开发

Moonbeam团队于2022年宣布开放Moonbuilders Academy。这是一套以开发为中心的异步学习课程,用于学习如何在Moonbeam上构建跨链DApp。 如何从官网进入平台? 点击http://moonbeam.network 鼠标移动至 “Builders”,在Resources下方选择“Moo…

激活函数整理

sigmoid函数 import torch from d2l import torch as d2l %matplotlib inline ​ xtorch.arange(-10,10,0.1,requires_gradTrue) sigmoidtorch.nn.Sigmoid() ysigmoid(x) ​ d2l.plot(x.detach(),y.detach(),x,sigmoid(x),figsize(5,2.5)) sigmoid函数连续、光滑、单调递增&am…

Queue接口分析

一、Queue是什么 该接口是Java集合框架成员 Queue: 通常(但不一定)队列就是一个先入先出(FIFO)的数据结构,和堆一样(但可以进行转换,比如优先级列队排序,又或者改为栈形…

增广路算法 DFS求解 最大网络流问题

最大网络流问题 最大网络流问题是这样的,有一个有向图,假定有一个源点,有一个汇点,源点有流量出来,汇点有流量进入,有向图上的边的权重为该条边可通过的最大流量(方向为边的方向),问从源点到汇…

网络安全新形势下的动态防御体系研究(下)

文章目录 一、动态防御体系构成(一)建立对抗式威胁评估能力(二)建立安全防御效能验证能力(三)建立安全防御基准检测能力(四)建立欺骗式防御能力(五)建立安全态…

Windows下面基于pgsql15的备份和恢复

一、基础备份 1.创建一个文件用来存储备份数据 2.备份指令 $CurrentDate Get-Date -Format "yyyy-MM-dd" $OutputDirectory "D:\PgsqData\pg_base\$CurrentDate" $Command "./pg_basebackup -h 127.0.0.1 -U postgres -Ft -Pv -Xf -z -Z5 -D $O…

前端面试题集合四(html)

HTML 面试知识点总结 本部分主要是笔者在复习 HTML 相关知识和一些相关面试题时所做的笔记,如果出现错误,希望大家指出! 目录 1. DOCTYPE 的作用是什么?2. 标准模式与兼容模式各有什么区别?3. HTML5 为什么只需要写…

Cesium笔记 初始化 原生Cesium

1、创建vue项目 vue create my_demo 2、下载Cesium 可以从官网下载&#xff0c;也可以使用node下载 npm install cesium 3、把node_modules文件夹中下载得Cesium&#xff0c;移出到public文件夹下 4、将Cesium.js 以及样式文件widgets.css在index.html中引用 <!DOCT…

鸿鹄云商B2B2C:JAVA实现的商家间直播带货商城系统概览

【saas云平台】打造全行业全渠道全场景的saas产品&#xff0c;为经营场景提供一体化解决方案&#xff1b;门店经营区域化、网店经营一体化&#xff0c;本地化、全方位、一站式服务&#xff0c;为多门店提供统一运营解决方案&#xff1b;提供丰富多样的营销玩法覆盖所有经营场景…

理清类图,辨识UML

类图 public 用 表示 protected 用 # 表示 private 用 - 表示 class Person { public:void setName(std::string name) {this->name name;}std::string getName() {return this->name;} public:int nationality;//国籍 protected:int idCard;//身份证号 private:std…

_Incapsula_Resource与Rc4混淆分析

一、获得混淆js 这么一个地址 https://www.interasia.cc/_Incapsula_Resource?SWJIYLWA5074a744e2e3d891814e9a2dace20bd4,719d34d31c8e3a6e6fffd425f7e032f3 浏览器打开这个地址 复制这个js&#xff0c;到浏览器调试 先格式化查看&#xff0c;也就是一个eval函数执行b函数 …

【解刊】Elsevier旗下,1区CCF-B,超快审稿:2个月22天录用!

计算机类 • 升区期刊 本次带来Elsevier旗下高分快刊&#xff0c;入选CCF-B类推荐&#xff0c;如有相关领域作者意向投稿&#xff0c;可重点关注&#xff01;更多领域期刊&#xff0c;可移步公众号【Unionpub学术】了解详情~ 01 期刊简介 Computer Networks ✅出版社&#x…

vue中使用component中的is渲染组件如何使用,:is 等价 v-if渲染组件。

动态组件顾名思义动态加载不同的组件&#xff0c;is属性用于加载不同组件&#xff0c;传参使用属性传递 1、使用v-for遍历component&#xff0c;组件都会执行 <componentv-for"(item, index) in TAB_PANE":key"index":is"item.componentName"…

idea右上角浏览器图标没有idea内部浏览器怎么显示

idea右上角浏览器图标没有idea内部浏览器怎么显示 file -> settings -> tools -> web brosers 选择需要的浏览器&#xff0c;勾选上展示到编辑器中 打开上图的Built-in Preview&#xff0c;就会显示idea标志的内部显示了&#xff01;&#xff01;&#xff01;

AttentionFreeTransformer 核心结构图(GraphViz 重绘)

AFTFull digraph AFTFull {rankdirBTnode [stylefilled, colorBlackfontcolorWhite, fillcolor"#30638e", fontname"SimHei",fontsize32,width5, height2,]inp [label"输入\n[BatchSize,\n SeqLen,\n HidSize]", shape"Mrecord"]llq […