八股(2)——Java异常与IO流

主要来自javaguide,加上自己的理解,放这里是方便自己时不时打开看看。后续每一次看的时候应该都会逐渐换成自己最新的理解。

什么是异常?

“有了异常处理机制后,程序在发生异常的时候就不会中断,我们可以对异常进行捕获,然后改变程序执行的流程。”

“除此之外,异常处理机制可以保证我们向用户提供友好的提示信息,而不是程序原生的异常信息——用户根本理解不了。”

Exception 和 Error 有什么区别?

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:

  • Exception :在可控范围内,程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • Error :意味着程序出现了严重的问题,而这些问题不应该再交给 Java 的异常处理机制来处理,程序应该直接崩溃掉,比如说 OutOfMemoryError,内存溢出了,这就意味着程序在运行时申请的内存大于系统能够提供的内存,导致出现的错误,这种错误的出现,对于程序来说是致命的。

Checked Exception 和 Unchecked Exception 有什么区别?

Java 异常类层次结构图概览

在这里插入图片描述

Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundExceptionSQLException…。

Unchecked Exception不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。

RuntimeException 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):

  • NullPointerException(空指针错误)
  • IllegalArgumentException(参数错误比如方法入参类型错误)
  • NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)
  • ArrayIndexOutOfBoundsException(数组越界错误)
  • ClassCastException(类型转换错误)
  • ArithmeticException(算术错误)
  • SecurityException (安全错误比如权限不够)
  • UnsupportedOperationException(不支持的操作错误比如重复创建同一用户)

Throwable 类常用方法有哪些?

  • String getMessage(): 返回异常发生时的简要描述
  • String toString(): 返回异常发生时的详细信息
  • String getLocalizedMessage(): 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
  • void printStackTrace(): 在控制台上打印 Throwable 对象封装的异常信息

try-catch-finally 如何使用?

  • try块 : 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • catch块 : 用于处理 try 捕获到的异常。
  • finally 块 : 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

代码示例:

try {
    System.out.println("Try to do something");
    throw new RuntimeException("RuntimeException");
} catch (Exception e) {
    System.out.println("Catch Exception -> " + e.getMessage());
} finally {
    System.out.println("Finally");
}

输出:

Try to do something
Catch Exception -> RuntimeException
Finally

注意:不要在 finally 语句块中使用 return! 因为会覆盖前面的return。下面代码中,try和finally中都有return,但是try里面的会被finally覆盖掉,不管try里面是什么,都最后会返回0。

代码示例:

public static void main(String[] args) {
    System.out.println(f(2));
}

public static int f(int value) {
    try {
        return value * value;  //本来应该返回4
    } finally {
        if (value == 2) {  //但是此时这个条件成立,返回0,覆盖了try中的返回4
            return 0;
        }
    }
}

输出:

0

finally 中的代码一定会执行吗?

不一定!

  1. finally 之前虚拟机被终止运行(System.exit(1);
  2. 程序所在的线程死亡。
  3. 关闭 CPU。

如何使用 try-with-resources 代替try-catch-finally

  1. 适用范围(资源的定义): 任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象
  2. 关闭资源和 finally 块的执行顺序:try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运行

《Effective Java》中明确指出:

面对必须要关闭的资源,我们总是应该优先使用 try-with-resources 而不是try-finally。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。try-with-resources语句让我们更容易编写必须要关闭的资源的代码,若采用try-finally则几乎做不到这点。

Java 中类似于InputStreamOutputStreamScannerPrintWriter等的资源都需要我们调用close()方法来手动关闭,一般情况下我们都是通过try-catch-finally语句来实现这个需求,如下:

//读取文本文件的内容
Scanner scanner = null;
try {
    scanner = new Scanner(new File("D://read.txt"));
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if (scanner != null) {
        scanner.close();
    }
}

使用 Java 7 之后的 try-with-resources 语句改造上面的代码:

try (Scanner scanner = new Scanner(new File("test.txt"))) {
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
}

当然多个资源需要关闭的时候,使用 try-with-resources 实现起来也非常简单,如果你还是用try-catch-finally可能会带来很多问题。

通过使用分号分隔,可以在try-with-resources块中声明多个资源。

try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
     BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
    int b;
    while ((b = bin.read()) != -1) {
        bout.write(b);
    }
}
catch (IOException e) {
    e.printStackTrace();
}

异常使用有哪些需要注意的地方?

  • 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。
  • 抛出的异常信息一定要有意义。
  • 建议抛出更加具体的异常比如字符串转换为数字格式错误的时候应该抛出NumberFormatException而不是其父类IllegalArgumentException
  • 使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。

何谓反射?

一般我们是new 类名()方式来获取一个类的对象 (静态的类加载),但是有时候执行哪一个类的哪一个方法,是由前一个任务的执行结果决定的,所以需要一种在运行期动态的改变程序的调用行为的方法

java的反射机制能够做那些事呢?大概是这样几种:

  • 在程序运行期动态的根据package名.类名实例化类对象
  • 在程序运行期动态获取类对象的信息,包括对象的成本变量和方法
  • 在程序运行期动态使用对象的成员变量属性
  • 在程序运行期动态调用对象的方法(私有方法也可以调用)

反射的优缺点?

反射可以让我们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利。

不过,反射让我们在运行时有了分析操作类的能力的同时,也增加了安全问题,比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。

反射的应用场景?

像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。但是!这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。

这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。

另外,像 Java 中的一大利器 注解 的实现也用到了反射。

为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?

这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。

何谓 SPI?

SPI 即 Service Provider Interface ,字面意思就是:“服务提供者的接口”,我的理解是:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。

SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。

很多框架都使用了 Java 的 SPI 机制,比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。

SPI 和 API 有什么区别?

  • API 其实是服务提供方,它把接口和实现都做了,然后提供给服务调用方来用,服务提供方处于主导地位
  • SPI 则是服务调用方去定义了某种标准(接口),你要给我提供服务,就必须按照我的这个标准来做实现,此时服务调用方处于主导

SPI 的优缺点?

通过 SPI 机制能够大大地提高接口设计的灵活性,但是 SPI 机制也存在一些缺点,比如:

  • 需要遍历加载所有的实现类,不能做到按需加载,这样效率还是相对较低的。
  • 当多个 ServiceLoader 同时 load 时,会有并发问题。

什么是序列化?什么是反序列化?

网络传输的都是二进制数据,但是在Java中都是对象,所以要序列化;反过来,想让别人知道信息是什么,还需要反序列化。

简单来说:

  • 序列化: 将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class)。

综上:序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。

如果有些字段不想进行序列化怎么办?

对于不想进行序列化的变量,使用 transient 关键字修饰。

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。

关于 transient 还有几点注意:

  • transient 只能修饰变量,不能修饰类和方法。
  • transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0
  • static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。

Java IO 流了解吗?

IO流简介及分类

IO 即 Input/Output,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。

Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

IO流主要的分类方式有以下3种:

  1. 按数据流的方向:输入流、输出流。读取文件是输入流,写文件是输出流。
  2. 按处理数据单位:字节流、字符流
  3. 按功能:节点流、处理流
I/O 流为什么要分为字节流和字符流呢?

问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

个人认为主要有两点原因:

  • 字符流专门操作文本文档,操作的是数据单元为16位的字符,是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时;
  • 字节流是万能流,一切皆字节,操作的单元是数据单元为8位的字节。但是如果我们不知道编码类型的话,使用字节流的过程中很容易出现乱码问题。(比如说一个中文为2个字节,一旦将一个字符对应的字节分裂开来,就会出现乱码了)
字节输入流(InputStream)

java.io.InputStream字节输入流超类(父类),我们来看一下它的一些共性方法:

1、close() :关闭此输入流并释放与此流相关的系统资源。

2、int read(): 从输入流读取数据的下一个字节。

3、read(byte[] b): 该方法返回的 int 值代表的是读取了多少个字节,读到几个返回几个,读取不到返回-1

FileInputStream类

InputStream最简单的一个子类。文件输入流,用于从文件中读取数据。

FileInputStream的构造方法:

  • FileInputStream(String name):创建一个 FileInputStream 对象,并打开指定名称的文件进行读取。文件名由 name 参数指定。如果文件不存在,将会抛出 FileNotFoundException 异常。
  • FileInputStream(File file):创建一个 FileInputStream 对象,并打开指定的 File 对象表示的文件进行读取。

FileInputStream读取字节数据:

  • read()方法会读取一个字节并返回其整数表示。如果已经到达文件的末尾,则返回 -1。如果在读取时发生错误,则会抛出 IOException 异常。
  • read(byte[] b) 方法会从输入流中最多读取 b.length 个字节,并将它们存储到缓冲区数组 b 中。
  • 还可以复制图片,把图片信息读入到字节输入流中,再通过字节输出流写入到文件中。
字节输出流(OutputStream)

java.io.OutputStream字节输出流超类(父类),我们来看一下它定义的一些共性方法:

1、 close() :关闭此输出流并释放与此流相关联的系统资源。

2、 flush() :刷新此输出流并强制缓冲区的字节被写入到目的地。

3、 write(byte[] b):将 b.length 个字节从指定的字节数组写入此输出流。

4、 write(byte[] b, int off, int len) :从指定的字节数组写入 len 字节到此输出流,从偏移量 off开始。 也就是说从off个字节数开始一直到 len 个字节结束

FileOutputStream类

OutputStream最简单的一个子类,文件输出流,用于将数据写入到文件。

FileOutputStream 构造方法

  • 使用文件名创建 FileOutputStream 对象

    String fileName = "example.txt";  
    //使用文件名 "example.txt" 创建一个 FileOutputStream 对象
    FileOutputStream fos = new FileOutputStream(fileName);
    
  • 使用文件对象创建 FileOutputStream 对象。

    File file = new File("example.txt");
    FileOutputStream fos = new FileOutputStream(file);
    

FileOutputStream 写入字节数据:

write(int b) //写入字节
write(byte[] b)  //写入字节数组
write(byte[] b,int off,int len)  //写入指定长度字节数组。从`off`索引开始,`len`个字节
字符输入流(Reader)

字符流 = 字节流 + 编码表,专门用于处理文本文件(音频、图片、视频等不算)

java.io.Reader字符输入流超类(父类),它定义了字符输入流的一些共性方法:

  • 1、close():关闭此流并释放与此流相关的系统资源。
  • 2、read():从输入流读取一个字符。
  • 3、read(char[] cbuf):从输入流中读取一些字符,并将它们存储到字符数组 cbuf

FileReader 是 Reader 的子类,用于从文件中读取字符数据。

FileReader 构造方法:

// 1、FileReader(File file):创建一个新的 FileReader,参数为File对象。
File file = new File("a.txt");
FileReader fr = new FileReader(file);

// 、FileReader(String fileName):创建一个新的 FileReader,参数为文件名。
FileReader fr = new FileReader("b.txt");

FileReader 读取字符数据:

  • 读取字符read方法,每次可以读取一个字符,返回读取的字符(转为 int 类型),当读取到文件末尾时,返回-1
  • 读取指定长度的字符read(char[] cbuf, int off, int len),并将其存储到字符数组中。其中,cbuf 表示存储读取结果的字符数组,off 表示存储结果的起始位置,len 表示要读取的字符数。
字符输出流(Writer)

java.io.Writer字符输出流类的超类(父类),可以将指定的字符信息写入到目的地,来看它定义的一些共性方法:

  • 1、write(int c) 写入单个字符。
  • 2、write(char[] cbuf) 写入字符数组。
  • 3、write(char[] cbuf, int off, int len) 写入字符数组的一部分,off为开始索引,len为字符个数。
  • 4、write(String str) 写入字符串。
  • 5、write(String str, int off, int len) 写入字符串的某一部分,off 指定要写入的子串在 str 中的起始位置,len 指定要写入的子串的长度。
  • 6、flush() 刷新该流的缓冲。
  • 7、close() 关闭此流,但要先刷新它。

java.io.FileWriter 类是 Writer 的子类,用来将字符写入到文件。

java.io.FileWriter 构造方法:

// FileWriter(File file): 创建一个新的 FileWriter,参数为要读取的File对象。
File file = new File("a.txt");
FileWriter fw = new FileWriter(file);

// FileWriter(String fileName): 创建一个新的 FileWriter,参数为要读取的文件的名称。
FileWriter fw = new FileWriter("b.txt");

java.io.FileWriter 写入数据:

  • 写入字符write(int b) 方法,每次可以写出一个字符
  • 写入字符数组write(char[] cbuf) 方法,将指定字符数组写入输出流
  • 写入指定字符数组write(char[] cbuf, int off, int len) ,将指定字符数组的一部分写入输出流
  • 写入字符串write(String str) 方法,将指定字符串写入输出流
  • 写入指定字符串write(String str, int off, int len) 方法,将指定字符串的一部分写入输出流
关闭close和刷新flush

因为 FileWriter 内置了缓冲区 ByteBuffer,所以如果不关闭输出流,就无法把字符写入到文件中。但是关闭了流对象,就无法继续写数据了。如果我们既想写入数据,又想继续使用流,就需要 flush 方法了。

flush :刷新缓冲区,流对象可以继续使用。

close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

注意!!!即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。当然你也可以用 try-with-resources 的方式。

字节缓冲流

Java 的缓冲流是对字节流和字符流的一种封装,通过在内存中开辟缓冲区来提高 I/O 操作的效率。Java 通过 BufferedInputStreamBufferedOutputStream 来实现字节流的缓冲,通过 BufferedReaderBufferedWriter 来实现字符流的缓冲。

缓冲流的工作原理是将数据先写入缓冲区中,当缓冲区满时再一次性写入文件或输出流,或者当缓冲区为空时一次性从文件或输入流中读取一定量的数据。这样可以减少系统的 I/O 操作次数,提高系统的 I/O 效率,从而提高程序的运行效率。

构造方法:

  • BufferedInputStream(InputStream in) :创建一个新的缓冲输入流,注意参数类型为InputStream
  • BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流,参数类型为OutputStream
// 创建字节缓冲输入流,先声明字节流
FileInputStream fps = new FileInputStream(b.txt);
BufferedInputStream bis = new BufferedInputStream(fps)

// 创建字节缓冲输入流(一步到位)
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("b.txt"));

// 创建字节缓冲输出流(一步到位)
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));

为什么字节缓冲流会这么快?

传统的 Java IO 是阻塞模式的,它的工作状态就是“读/写,等待,读/写,等待。。。。。。”

字节缓冲流解决的就是这个问题:一次多读点多写点,减少读写的频率,用空间换时间

字符缓冲流

BufferedReader 类继承自 Reader 类,提供了一些便捷的方法,例如 readLine() 方法可以一次读取一行数据,而不是一个字符一个字符地读取。

BufferedWriter 类继承自 Writer 类,提供了一些便捷的方法,例如 newLine() 方法可以写入一个系统特定的行分隔符。

构造方法:

  • BufferedReader(Reader in) :创建一个新的缓冲输入流,注意参数类型为Reader
  • BufferedWriter(Writer out): 创建一个新的缓冲输出流,注意参数类型为Writer
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("b.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));

字符缓冲流特有方法:

字符缓冲流的基本方法与普通字符流调用方式一致,这里不再赘述,我们来看字符缓冲流特有的方法。

  • BufferedReader:String readLine(): 读一行数据,读取到最后返回 null
  • BufferedWriter:newLine(): 换行,由系统定义换行符。

Java IO 中的设计模式有哪些?

装饰器(Decorator)模式

在不改变原有对象的情况下拓展其功能。就好比我想吃蛋糕,但可以给蛋糕同时加上草莓、芒果、樱桃作为点缀。

  • 主要用于解决继承关系复杂的问题,通过组合和委托来替代继承。
  • 主要作用就是给组件添加增强功能,可以在组件功能代码的前面、后面添加自己的功能代码,甚至可以将组件的功能代码完全替换掉。
  • 装饰器和具体的组件都继承相同的抽象类或接口(组件),所以可以使用无数个装饰器包装一个具体的组件。
  • 装饰器模式也是有问题的,它会导致设计中出现许多小类,如果过度使用,会让程序变得很复杂。
适配器(Adapter Pattern)模式

是一种结构型设计模式,用于将一个类的接口转换成客户端所期望的另一个接口。它允许不兼容接口的类可以一起工作,而无需修改其源代码。

当你使用一部只有苹果插口的充电器**(适配者 Adaptee),但你的手机是Type-C接口(目标接口 Target Interface)的时候,你需要一个转换器(适配器 Adapter)**来解决充电的问题。

在适配器模式中,有三个主要角色:

  1. 目标接口(Target Interface):客户端所期望的接口。客户端通过这个接口与适配器进行交互。
  2. 适配器(Adapter):实现了目标接口,并且包装了一个或多个不兼容接口的对象。它负责将客户端的请求转换成对适配者的相应调用。
  3. 适配者(Adaptee):需要被适配的类或接口。
工厂(Factory Pattern)模式

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。NIO 中大量用到了工厂模式。

在工厂模式中,我们创建对象时不会对客户端暴露创建逻辑,并且通过使用一个共同的接口来指向新创建的对象。

实例:当你去餐厅点菜时,服务员将你的点菜信息传递给厨师,厨师负责根据你的要求准备食物。在这个过程中,服务员就像是一个工厂,负责根据客户的需求实例化不同的菜品(产品),而厨师就像是工厂内部的具体工厂,负责根据服务员传递过来的菜品信息来制作具体的菜品。这样,你不需要直接与厨师打交道,也不需要了解食物是如何准备的,只需要告诉服务员你想要的菜品,就可以得到你想要的食物。

优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个新的产品,只要扩展一个新工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。

缺点: 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

观察者(Observer Pattern)模式

又名 发布/订阅模式,属于行为模式,定义了对象中一对多的依赖关系,让多个观察者(Observer)观察同一主题(Subject) ,当这个主题发生变化时,所有的观察者对象会被通知并被自动更新。

比如直播节目是主题,观众是观察者。每当直播节目有新的内容时,直播平台会自动通知所有正在观看的观众,告诉他们有新的内容可供观看。观众无需不停地刷新页面或者重新搜索直播,他们只需保持连接,系统会自动将新的内容推送给他们。观众接收到通知后就会更新他们的观看状态。

NIO 中的文件目录监听服务使用到了观察者模式。

NIO 中的文件目录监听服务基于 WatchService 接口和 Watchable 接口。WatchService 属于观察者,Watchable 属于被观察者。

BIO、NIO 和 AIO 的区别?

当你去购物时,BIO、NIO和AIO就像不同的购物方式:

  1. BIO(Blocking I/O)
    想象你在传统的商店购物,当你拿起一件蔬果想要买时,你必须排队等待蔬果称重开出单子。在这期间,你不能离开,必须等到结账完成才能继续购物。这就是BIO模型,当一个I/O操作进行时,程序会被阻塞,直到操作完成才能继续执行后续的任务
  2. NIO(Non-blocking I/O)
    现在想象你去了一个大型超市,它有许多自助结账台。你可以同时拿着多件商品,不用排队一个一个结账。你可以在扫描一件商品后,等待结账台的响应,然后扫描下一件商品,而不需要等待上一件商品结账完毕。这就是NIO模型,它允许一个线程同时管理多个I/O通道,不需要等待某个通道的操作完成,而是可以处理多个通道的操作请求
  3. AIO(Asynchronous I/O)
    现在再想象你使用了一个在线购物平台的购物车功能。你将商品加入购物车后,你可以继续浏览其他商品或者进行其他操作,而不需要等待购物车更新。当你的购物车发生变化时(比如价格调整),系统会自动通知你,告诉你购物车中的商品已经更新。这就是AIO模型,它是一种异步的I/O模型,你发起一个I/O操作后不需要等待其完成,而是可以继续执行其他任务,当操作完成时系统会通知你
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值