文章目录

  • 一. Java BIO
  • 二. File
  • 三. InputStream
  • 四. OutputStream
  • 五. Reader
  • 六. Writer
  • 七. Print___
  • 八. ZipStream
  • 九. BufferedStream
  • 十. 番外篇
  • 1️⃣、Properties
  • 2️⃣、其他 BIO类简介
  • 3️⃣、Java NIO
  • 4️⃣、Java AIO
  • 附录


java nio非阻塞体现在哪 java阻塞io和非阻塞io区别_Java

前置概念:

  • BIO ( Blocking I/O ) :同步并阻塞
  • NIO ( New I/O ) :同步非阻塞
  • AIO ( Asynchronous I/O ):异步非阻塞

同步与异步

  • 同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。
  • 异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。

同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。

阻塞和非阻塞

  • 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
  • 非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。

一. Java BIO

  1. Java中存在两种最基本的 IO流(字节流):
  • InputStream
  • OutputStream
  1. 在此基础上延伸出来了另外两种最基本的字符流
    (Reader与 Writer本质上是能按照当前编码格式自动编解码的 InputStream和 OutputStream)
  • Reader
  • Writer
  1. 关系表

-

字节流

字符流

输入流

InputStream

Reader

输出流

OutputStream

Writer

  1. 字节流可以处理一切文件,而字符流只能处理纯文本文件。
  2. IO流以内存为中心,输入与输出是相对于应用程序而言的。
  • input:从外部把数据读到内存中。
  • output:把数据从内存中输出到外部。
  1. IO流是一种顺序读写数据模式,它的特点是单向流动,一连串的数据(字符或字节),以先进先出的方式发送信息的通道。数据在其中就像自来水一样在水管中流动,所以把它称为 IO流。
  2. IO流以 byte[ ]为最小单位,byte支持的数据范围为:-128~127,在计算机中通常 8 byte等于1字节。
  3. Java中文字符默认采用 Unicode编码,其编解码方式不同于 UTF-8。
  • ASCII :一个英文字母为一个字节,一个中文汉字为两个字节。
  • Unicode:一个英文为一个字节,一个中文为两个字节
  • UTF-8 :一个英文字为一个字节,一个中文为三个字节
  • UTF-16 :一个英文字母或一个汉字都需要 2 个字节(一些汉字需要 4 个字节)。
  • UTF-32 :世界上任何字符的存储都需要 4 个字节。
  • 符号 :英文标点为一个字节,中文标点为两个字节。
  1. 关于文件读取路径,分为三种情况:
  • Linux类操作系统:/
  • Windows操作系统:\
    但在代码中\表示转义字符串,所以要用\\表示\
    同时绝对路径要以磁盘符号开头,如 C:\\Windows\\note.md
  • classpath类路径下:/ ,此种方式为可以采用、不采用
  1. 同步和异步:
  • 同步 IO指:读写 IO时,代码必须等待数据返回后才继续执行后续代码,它的优点是代码编写简单,缺点是 CPU执行效率低。
  • 异步 IO指:读写 IO时仅发出请求,然后立刻执行后续代码,它的优点是 CPU执行效率高,缺点是代码编写复杂。
  • Java标准库的包 java.io提供了同步IO,而 java.nio则是异步IO。
  • 以上讨论的InputStream、OutputStream、Reader、Writer全部都是同步 IO模型。
  1. 缓冲区的意义
    我们知道,程序与磁盘的交互相对于内存运算很慢,容易成为程序的性能瓶颈。减少程序与磁盘的交互,是提升程序效率一种有效手段。缓冲流,就应用这种思路:普通流每次读写一个字节,而缓冲流在内存中设置一个缓存区,缓冲区先存储足够的待操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。
  2. try( ) 语句诠释
    实际上编译器并不会特别地为 InputStream加上自动关闭。编译器只看 try(resource = …)中的对象是否实现了 java.lang.AutoCloseable接口,如果实现了,就自动加上 finally语句并调用 close( )方法。InputStream和 OutputStream都实现了这个接口,因此可以用在 try(resource)中。
  3. Java中对文件的读取采用了 Filter模式,也就是 Decorate 装饰者模式。
  4. 字符流底层默认使用到了缓冲区,而字节流没有。

二. File

  1. 简介:
  • 既可以表示文件,又可以表示目录。
  • 用来操作文件,不能操作文件中的数据。
  • File在构建对象时并不会产生真正的磁盘 IO操作,只有当被调用时才会发生磁盘 IO操作。所以当构建一个并不存在的文件或者目录时并不会发生错误。
  1. 获取当前系统的分隔符:
final String SEPARATOR = File.separator;
  1. 构造方法:
  2. 常用方法:
  3. 常用方法扩充与解释性说明:
  • long length():文件字节大小
  • mkdir() :创建新目录(父目录不存在则报错)
  • mkdirs():创建新目录(父目录不存在则自动创建)
  • delete():只有当目录为空时才能删除,否则报错。
  • createTempFile():创建临时文件,JVM退出时自动删除。
File f = File.createTempFile("tmp-", ".txt");
  1. 判断 是否存在以及类型
  • exists():是否存在
  • isDirectory():目录
  • isFile():文件

底层表示的码并不一样,分别为:存在、文件、目录、隐藏

  1. 遍历文件和目录
    使用 list()或者 listFiles()可以遍历文件和目录,两者的区别在于:
  • list:返回字符型数组,只能显示最后的文件名。
  • listFiles:返回文件对象数组,不仅能显示全部的路径,而且可以设置返回的限制条件。
String[] list = file.list();
File[] files  = file.listFiles();

File[] files2 = f.listFiles(new FilenameFilter() {
         public boolean accept(File dir, String name) {
            return name.endsWith(".exe"); // 仅列出 .exe文件
}});
  1. Path对象(了解):
    Java标准库提供了一个 Path对象,位于 java.nio.file包,Path对象与 File对象类似。
Path p1 = Paths.get(".", "project", "study"); 
Path p2 = p1.toAbsolutePath(); // 转换为绝对路径
File fe = p3.toFile(); 				 // 转换为File对象
  1. Files类
    从 JDK 7开始,Java提供了Files工具类,极大的方便了我们读写文件。
    简单范例:
  • 将某文件的全部内容读取为byte[]
byte[] data = Files.readAllBytes(Path.of("./file.txt"));
  • 如果是文本文件,则可以将其全部内容读取为String
// 默认使用UTF-8编码读取:
String content1 = Files.readString(Path.of("./file.txt"));

// 按行读取并返回每行内容:
List<String> lines = Files.readAllLines(Path.of("./file.txt"));

注意事项:

  • 此外,Files工具类还有copy()delete()exists()move()等快捷方法操作文件和目录。
  • 最后需要特别注意的是,Files提供的读写方法,受内存限制,只能读写小文件,例如配置文件等,不可一次读入几个G的大文件。读写大型文件仍然要使用文件流,每次只读写一部分文件内容。

三. InputStream

  1. 简介:
  • 是抽象类
  • 是所有输入流的父类
  1. read()方法诠释
  • 它是 InputStream类中最重要的方法
  • 它会读取输入流中的下一个字节,并返回字节表示的 int值(0~255)。如果已经读到末尾,则返回 -1表示结束。
  • 尽量使用有参的形式建立缓冲区,默认一次读取一个字节的方式效率很低。
public abstract int read() throws IOException;
  1. read() 返回值为什么是 Integer类型:
  • 返回值范围被定义为 0~255。
  • 字节在计算机中用补码表示、存在正负数,byte的范围是 -128~127,并不符合条件。
  • 采用 Integer + 高位补零的方式,只取后 8 byte的数据,我们就可以得到 0~255。
  1. read()有参、无参区分:
    两者虽然都是返回 int类型的数据,但是所表示的含义却大不相同!
  • int read():
    每次读取 1个字节,即 8 bit的数据。返回该 8 bit数据所表示的十进制数。
  • int read(byte[2048])本例中每次最多读取 2048个 byte的数据。返回实际读取到的 byte数量。
  1. 三种子类:
  • FileInputStream:从文件读取数据,是最终数据源;
  • ServletInputStream:从HTTP请求读取数据,是最终数据源;
  • Socket.getInputStream():从TCP连接读取数据,是最终数据源;
  1. 简单范例:(读取并打印)
public class Test01 {
    public static void main(String[] args) throws IOException {
        FileInputStream file = new FileInputStream("../1.txt");
        int n;
        StringBuilder   stringBuilder = new StringBuilder();
        while (true){
            n=file.read();
            if (n == -1) break;
            stringBuilder.append((char) n);
        }
        System.out.println(stringBuilder);
    }

四. OutputStream

  1. 简介:
  • 抽象类。
  • 大多数定义与 InputStream类似。
  1. write()方法诠释:
  • 它是 OutputStream中最主要的方法。
  • 虽然传入类型是 Integer,但是默认每次只写入一个字节,即取 Integer类型的后 8位。
public abstract void write(int b) throws IOException;
public void writeFile() throws IOException {
    OutputStream output = new FileOutputStream("./readme.txt");
    output.write(72); // H
    output.write(101); // e
    output.close();
}
  1. 字符串转 byte[ ],然后写入 OutputStream:
    getBytes()方法使我感到新颖)
public void writeFile() throws IOException {
    OutputStream output = new FileOutputStream("out/readme.txt");
    output.write("Hello".getBytes("UTF-8")); 
    output.close();
}
  1. flush()阐述
  • 作用:刷新,将缓冲区内容真正地输出到目的地。
  • 为什么存在?
    在我们向磁盘、网络写入数据时,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上 byte[ ]数组),等到缓冲区写满了,再一次性写入文件或者网络。
    对于很多 IO设备来说,一次写一个字节和一次写1000个字节,花费的时间几乎是完全一样的,所以 OutputStream有个 flush( )方法,能强制把缓冲区内容输出。
  • 何时使用?
    通常情况下我们并不需要调用该方法。当缓冲区写满数据时,OutputStream会自动调用,并且在调用 close( )方法关闭 OutputStream之前,也会自动调用方法。
    但是,在某些情况下我们必须手动去调用该方法,比如消息的及时更新机制。
  1. close()阐述
    在执行 close方法之前会默认先执行 flush方法刷新缓冲区。
  2. write方法 有参与无参 的区别:
  • 无参时:虽然每次读取的是 int类型的值,但是 write只会取的它的后 8位 byte数,然后当做一个字节读入内存当中。
  • 有参时:根据 byte[__] 的大小进行读取。比如 byte[ 2048 ] 则表示一次最多可读取 2048比特的数据,是无参时的 256倍。

五. Reader

  1. 简介:
  • 抽象类
  • 本质上是带编码转换器的 InputStream,能把 byte按当前编码格式转换为 char。

java nio非阻塞体现在哪 java阻塞io和非阻塞io区别_数据_02

  1. 主要方法:
    读取字符流的下一个字符,并返回字符表示的 int值,范围是 0~65535(2的16次方)。如果已读到末尾,返回-1。
public int read() throws IOException;
  1. FileReader子类,存在默认编码格式(与系统相同)。
public class Test01 {
    public static void main(String[] args) throws IOException {
        Reader reader = new FileReader("./1.txt");
        while(true) {
            int n = reader.read(); // 反复调用read()方法,直到返回-1
            if (n == -1) break;
            System.out.print((char)n); 
        }		reader.close(); }}
  1. 指定编码格式
  • JDK8
InputStream stream = new FileInputStream("./1.txt");
FileReader fileReader = new  InputStreamReader(stream,"UTF-8");
  • JDK13
Reader reader = new FileReader("./1.txt","UTF-8");
  1. 建立缓冲区
char[] buffer = new char[1024];
  1. InputStreamReader
  • 可以把任意的 InputStream转换为 Reader
InputStream input = new FileInputStream("src/readme.txt");

// 变换为Reader:
Reader reader = new InputStreamReader(input, "UTF-8");

六. Writer

  1. 简介:
    与 Reader相反,Writer是带编码转换器的 OutputStream,它把 char转换为 byte并输出。
    Writer是所有字符输出流的父类。

七. Print___

代指 PrintStream与 PrintWriter

  1. PrintStreamPrintWriter的区别
  • PrintStream本质是 FilterOutputStream。
  • PrintWriter本质是 Writer。
  • PrintStream最终输出的是 byte数据,而 PrintWriter最终输出的是 char数据。
  • 除此之外两者的使用方法几乎是一模一样的。
  1. 构造方法参数:
  2. 常用方法:
  • print(int):不支持换行
  • print(boolean)
  • print(String)
  • print(Object)
  • println( ... ):支持换行
  1. 注意点:
    我们经常使用的System.out.println(),其里面包含的 System.out就是系统默认提供的 PrintStream,表示标准输出。
    而标准错误输出采用System.err表示。

八. ZipStream

  1. 简介:
  • 分为 ZipInputStream 与 ZipOutputStream。
  • ZipInputStream是一种 FilterInputStream,它可以直接读取zip包的内容。
  • ZipOutputStream是一种 FilterOutputStream,它可以直接将内容写入到 zip包。
  1. 继承结构图
  2. ZipInputStream使用步骤:
  • 创建对象
  • 循环调用 getNextEntry ( ) 获取 ZipEntry对象,直到返回 null。
    (一个 ZipEntry表示一个压缩文件或者目录)
  • 判断类型
  • 文件:使用 read ( ) 方法读取,直到返回 -1。
  • 目录:跳过本次循环

**注意点:**判断时用的是 ZipEntry对象,但是读取时用的是 ZipInputStream对象。

  1. 简单实现
public class Test01 {
  public static void main(String[] args) throws IOException {
      
      InputStream    file    = new FileInputStream("3.zip");
      ZipInputStream zip     = new ZipInputStream(file);
      ZipEntry       entry;
      StringBuilder  builder = new StringBuilder();
      int            read    = 0;
     
      while ((entry = zip.getNextEntry()) != null) {
          if (!entry.isDirectory()) {
              while (true) {
                  read = zip.read();
                  if (read == -1)
                      break;
                  builder.append((char) read);
              }}System.out.println(builder);}}}
  1. ZipOutputStream使用步骤:
  • 创建对象
  • 每写入一个文件前,调用 putNextEntry( )方法
  • 然后用 write( )写入 byte[ ]数据
  • 最后再调用 closeEntry( )结束这个文件的打包。

九. BufferedStream

  1. 简介:
  • 字节缓冲流:BufferedInputStream 与 BufferedOutputStream
  • 字符缓冲流:BufferedReader 与 BufferedWriter
  • 为高效率而设计,但是真正的读写操作还是靠 FileOutputStream和 FileInputStream。
  1. 默认缓冲区大小
DEFAULT_BUFFER_SIZE = 8192;
  1. BufferedReader 存在readLine()方法,其他不存在。

十. 番外篇

1️⃣、Properties

  1. 简介:
    Properties(Java.util.Properties),该类主要用于读取Java的配置文件,不同的编程语言有自己所支持的配置文件,配置文件中很多变量是经常改变的,为了方便用户的配置,能让用户够脱离程序本身去修改相关的变量设置。就像在Java中,其配置文件常为 .properties文件,是以键值对的形式进行参数配置的。
    Properties继承自Hashtable,而 Hashtable又实现了 Map接口,所以 Properties里可以使用 get与 put 方法的,但由于方法的参数对象是 Object 而不是 String,所以一般不用。常用方法是 setProperties或者 getProperties。
  2. 使用步骤:
  • 创建 Properties对象
  • load加载指定文件
  • 用键获取值
  1. 简单实现
public class Test01 {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.load(new FileInputStream("./1.properties"));
        System.out.println(properties.getProperty("a"));;
    }
}

2️⃣、其他 BIO类简介

  1. DataInputStream 数据输入流,它是用来装饰其它输入流,作用是“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。
  2. PipedInputStream 管道字节输入流,能实现多线程间的管道通信。
  3. ByteArrayInputStream 字节数组输入流,从字节数组 ( byte[ ] ) 中进行以字节为单位的读取,也就是将资源文件都以字节的形式存入到该类中的字节数组中去。
  4. FilterInputStream 装饰者类,具体的装饰者继承该类,这些类都是处理类,作用是对节点类进行封装,实现一些特殊功能。
  5. ObjectInputStream 对象输入流,用来提供对基本数据或对象的持久存储。通俗点说,也就是能直接传输对象,通常应用在反序列化中。它也是一种处理流,构造器的入参是一个InputStream的实例对象。
  6. PipedReader 管道字符输入流。实现多线程间的管道通信。
  7. CharArrayReader 从 Char数组中读取数据的介质流。
  8. StringReader 从 String中读取数据的介质流。

3️⃣、Java NIO

  1. 简介:
  • NIO:New IO即 新IO,Non-blocking非堵塞
  • BIO面向流,NIO面向块(缓冲区)
  • Java 1.4中引入了 NIO框架,NIO 核心组件包括:
  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(选择器)

整个NIO体系包含的类远远不止这三个,只能说这三个是NIO体系的“核心API”。

  • 任何时候访问 NIO中的数据,都是通过缓冲区进行操作。
  • NIO 通过Channel(通道) 进行读写:通道是双向的,可读也可写,而流的读写是单向的。
  • 无论读写,通道只能和Buffer交互。也是因为 Buffer,所以通道可以异步地读写。
  • Selector是一个对象,它可以注册到很多个Channel上,监听各个Channel上发生的事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连接了。
  • Selectors 用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。
  1. 为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?
  • 编程复杂、不好用。
  • JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%
  • 项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高。

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

  1. CSDN NIO详解文章:链接
  2. 简单实现( copy程序 ):
public static void copyFileUseNIO(String src,String dst) throws IOException{
          FileInputStream fi=new FileInputStream(new File(src));
          FileOutputStream fo=new FileOutputStream(new File(dst));

          FileChannel inChannel=fi.getChannel();
          FileChannel outChannel=fo.getChannel();

          ByteBuffer buffer=ByteBuffer.allocate(1024);
          while(true){
              int eof =inChannel.read(buffer);
              if(eof==-1){  break;  }		//判断是否读完文件
              buffer.flip();
              outChannel.write(buffer);
              buffer.clear();
          }
          inChannel.close(); outChannel.close();
          fi.close();fo.close();
}
  1. Selector管理示意图

java nio非阻塞体现在哪 java阻塞io和非阻塞io区别_Java_03

4️⃣、Java AIO

  1. 简介:
  • AIO:Asynchronous I/O,即 NIO 2。
  • Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。
  • 异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
  1. 感想:其实这几种思想感觉在 Java中用的比较少,相反在其他软件中用的比较多。

附录

java nio非阻塞体现在哪 java阻塞io和非阻塞io区别_Java_04