Java IO流(精简版)

1

前置概念:

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

同步与异步

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

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

阻塞和非阻塞

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

一. Java BIO

  1. Java中存在两种最基本的 IO流(字节流):

    • InputStream
    • OutputStream
  2. 在此基础上延伸出来了另外两种最基本的字符流

    (Reader与 Writer本质上是能按照当前编码格式自动编解码的 InputStream和 OutputStream)

    • Reader
    • Writer
  3. 关系表

    -字节流字符流
    输入流InputStreamReader
    输出流OutputStreamWriter
  4. 字节流可以处理一切文件,而字符流只能处理纯文本文件。

  5. IO流以内存为中心,输入与输出是相对于应用程序而言的。

    • input:从外部把数据读到内存中。
    • output:把数据从内存中输出到外部。
  6. IO流是一种顺序读写数据模式,它的特点是单向流动,一连串的数据(字符或字节),以先进先出的方式发送信息的通道。数据在其中就像自来水一样在水管中流动,所以把它称为 IO流。

  7. IO流以 byte[ ]为最小单位,byte支持的数据范围为:-128~127,在计算机中通常 8 byte等于1字节。

  8. Java中文字符默认采用 Unicode编码,其编解码方式不同于 UTF-8。

    • ASCII :一个英文字母为一个字节,一个中文汉字为两个字节。
    • Unicode:一个英文为一个字节,一个中文为两个字节
    • UTF-8 :一个英文字为一个字节,一个中文为三个字节
    • UTF-16 :一个英文字母或一个汉字都需要 2 个字节(一些汉字需要 4 个字节)。
    • UTF-32 :世界上任何字符的存储都需要 4 个字节。
    • 符号 :英文标点为一个字节,中文标点为两个字节。
  9. 关于文件读取路径,分为三种情况:

    • Linux类操作系统:/

    • Windows操作系统:\

      ​ 但在代码中\表示转义字符串,所以要用\\表示\

      ​ 同时绝对路径要以磁盘符号开头,如 C:\\Windows\\note.md

    • classpath类路径下:/ ,此种方式为可以采用、不采用

  10. 同步和异步:

    • 同步 IO指:读写 IO时,代码必须等待数据返回后才继续执行后续代码,它的优点是代码编写简单,缺点是 CPU执行效率低。
    • 异步 IO指:读写 IO时仅发出请求,然后立刻执行后续代码,它的优点是 CPU执行效率高,缺点是代码编写复杂。
    • Java标准库的包 java.io提供了同步IO,而 java.nio则是异步IO。
    • 以上讨论的InputStream、OutputStream、Reader、Writer全部都是同步 IO模型。
  11. 缓冲区的意义

    ​ 我们知道,程序与磁盘的交互相对于内存运算很慢,容易成为程序的性能瓶颈。减少程序与磁盘的交互,是提升程序效率一种有效手段。缓冲流,就应用这种思路:普通流每次读写一个字节,而缓冲流在内存中设置一个缓存区,缓冲区先存储足够的待操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。

  12. try( ) 语句诠释

    ​ 实际上编译器并不会特别地为 InputStream加上自动关闭。编译器只看 try(resource = …)中的对象是否实现了 java.lang.AutoCloseable接口,如果实现了,就自动加上 finally语句并调用 close( )方法。InputStream和 OutputStream都实现了这个接口,因此可以用在 try(resource)中。

  13. Java中对文件的读取采用了 Filter模式,也就是 Decorate 装饰者模式。

  14. 字符流底层默认使用到了缓冲区,而字节流没有。


二. File

  1. 简介:

    • 既可以表示文件,又可以表示目录。
    • 用来操作文件,不能操作文件中的数据。
    • File在构建对象时并不会产生真正的磁盘 IO操作,只有当被调用时才会发生磁盘 IO操作。所以当构建一个并不存在的文件或者目录时并不会发生错误。
  2. 获取当前系统的分隔符:

    final String SEPARATOR = File.separator;
    
  3. 构造方法:

    image-20220503131945348

  4. 常用方法:

    image-20220503132016630

  5. 常用方法扩充与解释性说明:

    • long length():文件字节大小

    • mkdir() :创建新目录(父目录不存在则报错)

    • mkdirs():创建新目录(父目录不存在则自动创建)

    • delete():只有当目录为空时才能删除,否则报错。

    • createTempFile():创建临时文件,JVM退出时自动删除。

      File f = File.createTempFile("tmp-", ".txt"); 
      
  6. 判断 是否存在以及类型

    • exists():是否存在
    • isDirectory():目录
    • isFile():文件

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

    image-20220502084448064
  7. 遍历文件和目录

    使用 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文件
    }});
    
  8. Path对象(了解):

    ​ Java标准库提供了一个 Path对象,位于 java.nio.file包,Path对象与 File对象类似。

    Path p1 = Paths.get(".", "project", "study"); 
    Path p2 = p1.toAbsolutePath(); // 转换为绝对路径
    File fe = p3.toFile(); 				 // 转换为File对象
    
  9. 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. 简介:

    • 是抽象类
    • 是所有输入流的父类
  2. read()方法诠释

    • 它是 InputStream类中最重要的方法

    • 它会读取输入流中的下一个字节,并返回字节表示的 int值(0~255)。如果已经读到末尾,则返回 -1表示结束。

    • 尽量使用有参的形式建立缓冲区,默认一次读取一个字节的方式效率很低。

    public abstract int read() throws IOException;
    
  3. read() 返回值为什么是 Integer类型:

    • 返回值范围被定义为 0~255。
    • 字节在计算机中用补码表示、存在正负数,byte的范围是 -128~127,并不符合条件。
    • 采用 Integer + 高位补零的方式,只取后 8 byte的数据,我们就可以得到 0~255。
  4. read()有参、无参区分:

    两者虽然都是返回 int类型的数据,但是所表示的含义却大不相同!

    • int read():

      每次读取 1个字节,即 8 bit的数据。返回该 8 bit数据所表示的十进制数。

    • int read(byte[2048])

      本例中每次最多读取 2048个 byte的数据。返回实际读取到的 byte数量。

  5. 三种子类:

    • FileInputStream:从文件读取数据,是最终数据源;
    • ServletInputStream:从HTTP请求读取数据,是最终数据源;
    • Socket.getInputStream():从TCP连接读取数据,是最终数据源;
  6. 简单范例:(读取并打印)

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类似。
  2. 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();
    }
    
  3. 字符串转 byte[ ],然后写入 OutputStream:

    getBytes()方法使我感到新颖)

    public void writeFile() throws IOException {
        OutputStream output = new FileOutputStream("out/readme.txt");
        output.write("Hello".getBytes("UTF-8")); 
        output.close();
    }
    
  4. flush()阐述

    • 作用:刷新,将缓冲区内容真正地输出到目的地。

    • 为什么存在?

      ​ 在我们向磁盘、网络写入数据时,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上 byte[ ]数组),等到缓冲区写满了,再一次性写入文件或者网络。

      ​ 对于很多 IO设备来说,一次写一个字节和一次写1000个字节,花费的时间几乎是完全一样的,所以 OutputStream有个 flush( )方法,能强制把缓冲区内容输出。

    • 何时使用?

      ​ 通常情况下我们并不需要调用该方法。当缓冲区写满数据时,OutputStream会自动调用,并且在调用 close( )方法关闭 OutputStream之前,也会自动调用方法。

      ​ 但是,在某些情况下我们必须手动去调用该方法,比如消息的及时更新机制。

  5. close()阐述

    ​ 在执行 close方法之前会默认先执行 flush方法刷新缓冲区。

  6. write方法 有参与无参 的区别:

    • 无参时:虽然每次读取的是 int类型的值,但是 write只会取的它的后 8位 byte数,然后当做一个字节读入内存当中。
    • 有参时:根据 byte[__] 的大小进行读取。比如 byte[ 2048 ] 则表示一次最多可读取 2048比特的数据,是无参时的 256倍。

五. Reader

  1. 简介:

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

    image-20220503092818635

  2. 主要方法:

    ​ 读取字符流的下一个字符,并返回字符表示的 int值,范围是 0~65535(2的16次方)。如果已读到末尾,返回-1。

    public int read() throws IOException;
    
  3. 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(); }}
    
  4. 指定编码格式

    • JDK8
    InputStream stream = new FileInputStream("./1.txt");
    FileReader fileReader = new  InputStreamReader(stream,"UTF-8");
    
    • JDK13
    Reader reader = new FileReader("./1.txt","UTF-8");
    
  5. 建立缓冲区

    char[] buffer = new char[1024];
    
  6. 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是所有字符输出流的父类。

    image-20220503103738680


七. Print___

代指 PrintStream与 PrintWriter

  1. PrintStreamPrintWriter的区别

    • PrintStream本质是 FilterOutputStream。
    • PrintWriter本质是 Writer。
    • PrintStream最终输出的是 byte数据,而 PrintWriter最终输出的是 char数据。
    • 除此之外两者的使用方法几乎是一模一样的。
  2. 构造方法参数:

    image-20220503221458094
  3. 常用方法:

    • print(int):不支持换行
    • print(boolean)
    • print(String)
    • print(Object)
    • println( ... ):支持换行
  4. 注意点:

    ​ 我们经常使用的System.out.println(),其里面包含的 System.out就是系统默认提供的 PrintStream,表示标准输出。

    ​ 而标准错误输出采用System.err表示。


八. ZipStream

  1. 简介:

    • 分为 ZipInputStream 与 ZipOutputStream。
    • ZipInputStream是一种 FilterInputStream,它可以直接读取zip包的内容。
    • ZipOutputStream是一种 FilterOutputStream,它可以直接将内容写入到 zip包。
  2. 继承结构图

    image-20220502222902739
  3. ZipInputStream使用步骤:

    • 创建对象

    • 循环调用 getNextEntry ( ) 获取 ZipEntry对象,直到返回 null。

      (一个 ZipEntry表示一个压缩文件或者目录)

    • 判断类型

      • 文件:使用 read ( ) 方法读取,直到返回 -1。
      • 目录:跳过本次循环

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

  4. 简单实现

    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);}}}
    
  5. ZipOutputStream使用步骤:

    • 创建对象
    • 每写入一个文件前,调用 putNextEntry( )方法
    • 然后用 write( )写入 byte[ ]数据
    • 最后再调用 closeEntry( )结束这个文件的打包。image-20220503085432771

九. BufferedStream

  1. 简介:

    • 字节缓冲流:BufferedInputStream 与 BufferedOutputStream
    • 字符缓冲流:BufferedReader 与 BufferedWriter
    • 为高效率而设计,但是真正的读写操作还是靠 FileOutputStream和 FileInputStream。
  2. 默认缓冲区大小

     DEFAULT_BUFFER_SIZE = 8192;
    
  3. 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加载指定文件
    • 用键获取值
  3. 简单实现

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 用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。

  2. 为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?

    • 编程复杂、不好用。
    • JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%
    • 项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高。

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

  3. CSDN NIO详解文章:链接

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

附录

Java BIO

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ThinkStu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值