articleList

02-Input、Output Stream流

2025/03/14 posted in  IO
Tags: 

第1集 Java核心字符编码和字符-字节流介绍

简介:Java核心字符编码和字符-字节流介绍

  • 电报的作用是解决什么问题?
image-20240520152822093
  • 什么是编码

    • 将信息从一种形式或格式转换为另一种形式或格式的过程,特别是在计算机科学和电信领域。

    • 在字符编码的上下文中,编码指的是将字符(如字母、数字、标点符号等)转换为计算机可以理解的二进制形式

    • 常用的有

      • ASCII 编码(American Standard Code for Information Interchange,美国信息交换标准代码)

        • 是一种基于拉丁字母的字符编码标准,它主要用于显示现代英语和其他西欧语言,包括控制符(如换行符和回车符)

        • 可显示的字符(如字母、数字、标点符号、制表符等),以及一些特殊字符

        • 只支持128个字符,因此它并不能表示所有语言的字符,尤其是像中文这样的复杂字符系统。

        • 为解决问题,开发各种扩展ASCII编码,如ISO-8859系列(用于西欧语言)、GB2312/GBK/GB18030(简体中文)等

      • UTF-8(8位元,Universal Character Set/Unicode Transformation Format)

        • 是针对Unicode的一种可变长度字符编码。
        • 它可以用来表示Unicode标准中的任何字符,包括世界上几乎所有的书写语言的字符
      • GBK编码

        • 是一种针对汉字的字符编码标准,也被称为GBK/GB2312。
        • 它是汉字编码国家标准GB 2312-1980的扩展,包含了GB2312编码中的全部汉字
        • 英文字母、数字、标点等非汉字字符仍然只占用一个字节,其编码值与ASCII码相同
      • ISO-8859-1编码

        • 是单字节编码,向下兼容ASCII编码。,它的编码范围是0x00-0xFF,其中0x00-0x7F之间完全和ASCII编码一致
        • 用于表示英语字符;0x80-0xFF之间用于表示其他语言字符,如西欧语言、希腊语等。
  • 什么是java的IO流

    • Java中的I/O(输入/输出)流是用于处理数据输入和输出的抽象类。
    • Java I/O流主要分为两大类:字节流(Byte Streams)和字符流(Character Streams)。
      • 字节流:用于处理二进制数据,包括InputStream和OutputStream两个主要类及其子类。
      • 字符流:用于处理文本数据,包括Reader和Writer两个主要类及其子类。
  • 字节和字符流区别

    • 处理的数据类型
      • 字节流
        • 处理的是字节数据,即8位二进制数据,在计算机中,所有的文件都能以二进制(字节)形式存在。
        • Java的I/O中针对字节传输操作提供了一系列流,统称为字节流。
        • 这些流包括两个抽象基类InputStream和OutputStream,分别处理字节流的输入和输出
      • 字符流
        • 处理的是字符数据,即Unicode字符,通常是16位二进制数据。
        • 字符流是16位unicode字符流,主要用于处理字符和文本文件。
        • 由于Java中字符是采用Unicode标准,因此字符流在处理文本数据时具有更高的效率和准确性。
    • 编码问题
      • 字节流
        • 因为直接操作的是字节,没有编码问题,字节流可以处理任意类型的数据,包括文本、图片、音频等。
        • 当使用字节流处理文本文件时,需要自行处理编码问题,否则可能会出现乱码
      • 字符流
        • Java使用Unicode编码来表示字符,而外部数据源可能使用不同的编码方式。
        • 字符流在读取或写入文本文件时,会自动进行字符编码的转换,使用字符流处理文本文件时通常不需要担心编码问题。
    • 使用场景
      • 字节流:
        • 字节流以字节(8bit)为单位,适合处理图片、视频、音频等二进制文件,以及网络传输等场景。
        • 由于字节流直接操作字节数据,因此具有更高的灵活性和效率
      • 字符流:
        • 字符流以字符为单位,根据码表映射字符,一次可能读多个字节,适合处理文本文件、文本数据等场景。
        • 字符流在处理文本数据时具有更高的效率和准确性,因为字符流会自动处理字符编码的转换
  • IO流相关类体系概览

    • 功能不同,但是具有共性内容,通过不断抽象形成4个抽象类,抽象类下面有很多子类是具体的实现

      • 字符流 Reader/Writer
      • 字节流 InputStream/OutputStream

image-20191023000405651

第2集 Java输入流InputStream案例实战

简介:讲解InputStream相关介绍及其子类

  • InputStream

    • 是输入字节流的父类,它是一个抽象类(一般用他的子类)
    • 在Java中,InputStream是所有字节输入流的超类。
    • 它定义了字节流输入的基本操作,如读取字节、跳过字节和标记/重置流等。
    • 通过InputStream,我们可以从文件、网络连接或其他数据源中读取字节数据。
  • 常见方法

    • int read()

      • 从输入流中读取单个字节,返回0到255范围内的int字节值, 字节数据可直接转换为int类型
      • 如果已经到达流末尾而没有可用的字节,则返回-1
    • int read(byte[] b)

      • 从输入流中读取最多b.length个字节的数据到字节数组b中,并返回实际读取的字节数。
      • 如果因为已经到达流末尾而没有更多的数据,则返回-1。

      image-20240520161336253

    • int read(byte[] b, int off, int len)

      • 从输入流中读取最多len个字节的数据到字节数组b中,从off指定的偏移量开始存储,并返回实际读取的字节数。
      • 如果因为已经到达流末尾而没有更多的数据,则返回-1。
    • long skip(long n)

      • 跳过输入流中的n个字节。如果实际跳过的字节数小于n,则可能是因为已经到达流的末尾。
      • 此方法返回实际跳过的字节数。
    • int available():返回可以从此输入流中读取的字节数的估计值。

    • void close():关闭此输入流并释放与该流关联的系统资源。

  • 常见子类

    • FileInputStream

      • 抽象类InputStream用来具体实现类的创建对象, 文件字节输入流, 对文件数据以字节的形式进行读取操作
      //常用构造函数,传入文件所在地址
      public FileInputStream(String name) throws FileNotFoundException
      
      //常用构造函数,传入文件对象
      public FileInputStream(File file) throws FileNotFoundException
      
    • ByteArrayInputStream 字节数组输入流

    • ObjectInputStream 对象输入流

    • ....还有很多

  • 案例实战

    public class InputStreamDemo {
    
        public static void main(String[] args) {
            String dir = "/Users/xdclass/Desktop/coding/xdclass-account/src/chapter11";
            String name = "a.txt";
            File file = new File(dir, name);
            try (InputStream inputStream = new FileInputStream(file)) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    // 处理读取到的数据(例如打印到控制台)
                    System.out.println(new String(buffer, 0, bytesRead));
                    //中文乱码问题,换成GBK 或者 UTF-8
                    //System.out.println(new String(buffer,"UTF-8"));
                    //System.out.println(new String(buffer,0, bytesRead,"UTF-8"));
                    //System.out.println(new String(buffer, 0, bytesRead));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 注意

    • 在上面的示例中,使用了try-with-resources语句来自动关闭InputStream
    • 这是JDK 7及更高版本引入的一个新特性,用于确保在不再需要资源时自动关闭它们。
  • 编码小知识(节省空间)

    • 操作的中文内容多则推荐GBK:

      • GBK中英文也是两个字节,用GBK节省了空间,
      • UTF-8 编码的中文使用了三个字节
    • 如果是英文内容多则推荐UFT-8:

      • 因为UFT-8里面英文只占一个字节
      • UTF-8编码的中文使用了三个字节
  • IDEA编码格式配置

image-20240521114040085

第3集 Java输出流 OutputStream案例实战

简介:讲解OutputStream相关介绍及其子类

  • OutputStream

    • 是输出字节流的父类,它是一个抽象类,在Java中,OutputStream是所有字节输出流的超类。
    • 它定义了字节流输出的基本操作,如写入字节、刷新输出流和关闭输出流等。
    • 通过OutputStream,可以将数据写入文件、网络连接或其他数据接收端。
  • OutputStream的主要方法

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

    image-20240520161336253

    • void write(byte[] b, int off, int len):从指定的字节数组写入len个字节,从偏移量off开始。
    • void flush()
      • 刷新此输出流并强制写出任何缓冲的输出字节,
      • 进行输出时,为了提高效率,这些类通常会实现缓存机制。
      • 当调用write()方法写入数据时,数据可能并不会立即被发送到目标位置,而是先被存储在内部缓冲区中。
      • 当缓冲区满或我们显式地调用flush()方法时
    • void close():关闭此输出流并释放与此流相关联的任何系统资源
  • OutputStream的子类

    • FileOutputStream,抽象类用来具体实现类的创建对象, 文件字节输出流, 对文件数据以字节的形式进行输出的操作
    //传入输出的文件地址
    public FileOutputStream(String name)
     
    //传入目标输出的文件对象
    public FileOutputStream(File file) 
    
    //传入目标输出的文件对象, 是否可以追加内容
    public FileOutputStream(File file, boolean append)
    
    • ByteArrayOutputStream:在内存中创建一个缓冲区,所有写入流的数据都会置入这个缓冲区。
    • ....还有很多
  • 案例实战

    public class OutputStreamExample {  
        public static void main(String[] args) {
        
            String dir = "/Users/xdclass/Desktop/coding/xdclass-account/src/chapter11";
            String name = "b.txt";
            
            try (OutputStream outputStream = new FileOutputStream(file)) {  
                String data = "Hello, World!";  
                outputStream.write(data.getBytes());  
                outputStream.flush(); // 确保所有数据都写入文件  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }
    
  • 注意:在Java中,通常会在以下情况下调用flush()方法:

    • 在完成所有写入操作并希望确保所有数据都被发送到目标位置时。
    • 在需要在写入过程中立即看到数据的效果时(例如,在网络编程中)。
    • 在关闭输出流之前,以确保所有缓冲的数据都被发送出去。

第4集 Java IO包之缓冲Buffer输入输出流介绍

简介: Java IO包之缓冲Buffer输入输出流介绍

  • 什么是缓冲Buffer

    • 它是内存空间的一部分,在内存空间中预留了一定的存储空间
    • 这些存储空间用来缓冲输入或输出的数据,这部分空间就叫做缓冲区,缓冲区是具有一定大小的
    • 当使用缓冲流时,数据首先被读写到缓存区中,然后再从缓存区传输到目标位置(如文件、网络等)。
    • 这种方式减少了直接对目标位置的读写操作,因此提高了性能。
  • 为啥要用缓冲

    • 缓冲,缓和冲击,例如操作磁盘比内存慢的很多,所以不用缓冲区效率很低,数据传输速度和数据处理的速度存在不平衡
    • 比如
      • 每秒要读写100次硬盘,对系统冲击很大,浪费了大量时间在忙着处理开始写和结束写这两个事件
      • 所以用内存的buffer暂存起来,变成每10秒写一次硬盘,数据可以直接送往缓冲区
    • 高速设备不用再等待低速设备,对系统的冲击就很小,写入效率高了
  • Java中的缓冲输入流与输出流(提供了四种Buffer流)

    • BufferedInputStream:缓存输入流,封装了InputStream,提供了缓存区来暂存输入数据。
    • BufferedOutputStream:缓存输出流,封装了OutputStream,提供了缓存区来暂存输出数据。
    • BufferedReader:缓存字符输入流,封装了Reader,提供了缓存区来暂存字符输入数据。
    • BufferedWriter:缓存字符输出流,封装了Writer,提供了缓存区来暂存字符输出数据。
    • 采用的装饰器设计模式(锦上添花)
  • 主要特点:由于使用了缓存区,因此减少了直接对目标位置的读写操作,从而提高了性能。

  • BufferedInputStream 缓冲字节输入流

    • 通过预先读入一整段原始输入流数据至缓冲区中,外界对BufferedInputStream的读取操作实际上是在缓冲区上进行,

    • 如果读取的数据超过了缓冲区的范围,BufferedInputStream负责重新从原始输入流中载入下一截数据,填充缓冲区

    • 然后外界继续通过缓冲区进行数据读取,避免了大量的磁盘IO,原始的InputStream类实现的read是即时读取的

    • 因为每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),如果数据量巨大,这样的磁盘消耗非常可怕。

    • 读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进行一次磁盘IO

    • 载入一段数据填充缓冲,下一次读取一般情况就直接可以从缓冲区读取,减少了磁盘IO。

    • 默认缓冲区大小是8k, int DEFAULT_BUFFER_SIZE = 8192;

    • 构造函数

      //对输入流进行包装,里面默认的缓冲区是8k
      public BufferedInputStream(InputStream in);
      
      //对输入流进行包装,指定创建具有指定缓冲区大小的
      public BufferedInputStream(InputStream in,int size);
      
    • 常用方法

      /从输入流中读取一个字节
      public int read();
      
      //从字节输入流中,给定偏移量offset处开始, 将len字节读取到指定的byte数组中。
      public int read(byte[] buf,int off,int len);
      
      //关闭释放资源,关闭的时候这个流即可,InputStream会在里面被关闭
      void close();
      
  • BufferedOutputStream 缓冲字节输出流

    • 内部使用一个缓冲区来暂存待写入的数据。
    • 当缓冲区满时,或者调用flush()方法时,缓冲区中的数据会被一次性写入到底层输出流中。
    • 这种机制提高了数据写入的效率,减少了系统I/O操作的次数。
    • 构造函数
    //对输出流进行包装,里面默认的缓冲区是8k
    public BufferedOutputStream(OutputStream out);
    
    //对输出流进行包装,指定创建具有指定缓冲区大小的
    public BufferedOutputStream(OutputStream out,int size);
    
    • 常用的三个方法
      //向输出流中输出一个字节
      public void write(int b);
    
      //将指定 byte 数组中从偏移量 off 开始的 len 个字节写入缓冲的输出流。
      public void write(byte[] buf,int off,int len);
    
      //刷新此缓冲的输出流,强制使所有缓冲的输出字节被写出到底层输出流中。
      public void flush();
    
      //关闭释放资源,关闭的时候这个流即可,OutputStream会在里面被关闭, JDK7新特性try(在这里声明的会自动关闭){}
      void close();
    
  • 注意点

    • 在使用完BufferedOutputStream后,一定要记得关闭它,释放系统资源,通过调用close()方法来实现关闭操作。
    • BufferedOutputStream在close()时会自动flush
     public void close() throws IOException {
          try (OutputStream ostream = out) {
              flush();
          }
      }
    
    • 在不调用close()的情况下,缓冲区不满,又需要把缓冲区的内容写入到文件或通过网络发送到别的机器时,才需要调用flush.
    • 流的关闭顺序: 后开先关, 如果A依赖B,先关闭B

第5集 新版缓冲Buffer输入输出流综合案例实战

简介: 新版缓冲Buffer输入输出流综合案例实战

  • 新版try-with-resource语法

    • 在Java中,从JDK 7开始,可以使用try-with-resources语句
    • 来自动管理实现了AutoCloseableCloseable接口的资源如文件流、缓冲流等。
    • 这种语法可以确保资源在try代码块执行完毕后被正确关闭,即使try块中的代码抛出了异常。
  • 缓冲流输入案例实战

    • 创建了一个FileInputStream并将其包装在BufferedInputStream中。
    • 通过bis.read(buffer)方法,可以从文件中读取数据到缓冲区中,并在读取过程中处理这些数据。
    • 在try代码块结束后,BufferedInputStreamFileInputStream都会被自动关闭
    import java.io.*;  
      
    public class BufferedInputStreamExample {  
        public static void main(String[] args) {  
            String inputFilePath = "input.txt"; // 假设这个文件存在并包含一些数据  
            try (FileInputStream fis = new FileInputStream(inputFilePath);  
                 BufferedInputStream bis = new BufferedInputStream(fis)) {  
      
                byte[] buffer = new byte[1024];  
                int bytesRead;  
      
                // 读取数据  
                while ((bytesRead = bis.read(buffer)) != -1) {  
                    // 处理读取到的数据(这里只是简单地打印出来)  
                    System.out.print(new String(buffer, 0, bytesRead));  
                }  
      
                // 注意:由于使用了try-with-resources,close()方法在这里是自动调用的  
      
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }
    
  • 缓冲流输出案例实战

    • 在这个例子中,创建了一个FileOutputStream并将其包装在BufferedOutputStream中。
    • 在try代码块结束后,BufferedOutputStreamFileOutputStream都会被自动关闭,因为它们都实现了AutoCloseable接口。
    import java.io.*;  
      
    public class BufferedOutputStreamExample {  
        public static void main(String[] args) {  
            String outputFilePath = "output.txt";  
            try (FileOutputStream fos = new FileOutputStream(outputFilePath);  
                 BufferedOutputStream bos = new BufferedOutputStream(fos)) {  
      
                // 写入数据到缓冲区  
                String data = "Hello, World!";  
                bos.write(data.getBytes());  
      
                // 注意:由于使用了try-with-resources,flush()和close()方法在这里是自动调用的  
      
                System.out.println("数据已成功写入文件。");  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }
    
  • 拓展旧版语法,流的关闭顺序: 后开先关, 如果A依赖B,先关闭B

    public class BufferedInputStreamExample {  
        public static void main(String[] args) {  
            try {  
                // 创建一个文件输入流  
                FileInputStream fis = new FileInputStream("example.txt");  
                  
                // 创建一个缓冲输入流,并封装文件输入流  
                BufferedInputStream bis = new BufferedInputStream(fis);  
                  
                // 定义一个字节数组用于存储读取的数据  
                byte[] buffer = new byte[1024];  
                int bytesRead;  
                  
                // 循环读取数据直到文件末尾  
                while ((bytesRead = bis.read(buffer)) != -1) {  
                    // 处理读取到的数据(这里只是简单地打印出来)  
                    System.out.println(new String(buffer, 0, bytesRead));  
                }  
                  
                // 关闭缓冲输入流和文件输入流  
                bis.close();  
                fis.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }
    
    
    public class BufferedOutputStreamExample {  
        public static void main(String[] args) {  
            try {  
                // 创建一个文件输出流  
                FileOutputStream fos = new FileOutputStream("output.txt");  
                  
                // 创建一个带缓冲区的输出流,并封装文件输出流  
                BufferedOutputStream bos = new BufferedOutputStream(fos);  
                  
                // 写入数据到缓冲区  
                String data = "Hello, World!";  
                bos.write(data.getBytes());  
                  
                // 刷新缓冲区,确保数据被写入文件  
                bos.flush();  
                  
                // 关闭输出流和底层流  
                bos.close();  
                fos.close();  
                  
                System.out.println("数据已成功写入文件。");  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }
    

第6集 课程作业之Java实现文件的拷贝案例实战《上》

简介: 课程作业之Java实现文件的拷贝案例实战

  • 需求

    • 编写一个Java程序,实现将一个文件从源路径复制到目标路径的功能。
    • 在这个过程中,你需要使用BufferedInputStreamBufferedOutputStream来提高文件传输的效率。
  • 作业思路参考

    • 定义一个copyFile方法,该方法接收两个参数:源文件的路径和目标文件的路径
    • copyFile方法中,使用BufferedInputStreamBufferedOutputStream来读取源文件内容并写入到目标文件中。
    • 确保在拷贝文件的过程中,源文件和目标文件都可以是任意大小的文件,并且拷贝过程应该能够处理大文件。
    • 在拷贝完成后,输出一条消息到控制台,表示文件拷贝成功。
    • 如果在拷贝过程中发生任何异常(如源文件不存在、目标文件无法创建等),请捕获异常并输出一条错误消息到控制台。
    • 最后,编写一个main方法来测试你的copyFile方法。
  • 参考骨架案例

    import java.io.*;  
      
    public class FileCopier {  
      
        public static void copyFile(String sourceFilePath, String targetFilePath) {  
            // 在这里实现文件拷贝逻辑  
        }  
      
        public static void main(String[] args) {  
            // 示例源文件和目标文件路径  
            String sourceFilePath = "path/to/source/file.txt";  
            String targetFilePath = "path/to/target/file.txt";  
      
            // 调用copyFile方法  
            try {  
                copyFile(sourceFilePath, targetFilePath);  
                System.out.println("文件拷贝成功!");  
            } catch (Exception e) {  
                System.err.println("文件拷贝失败:" + e.getMessage());  
            }  
        }  
    }
    
  • 知识点进阶:Files和Paths类

    • java.nio.file 包是 Java 7 引入的一个新的文件系统 API,提供了更加灵活的文件 I/O 操作。

    • 这个包中的 FilesPaths 类是文件操作中非常常用的两个工具类

    • Files 类提供了一系列静态方法,用于在文件系统中执行各种操作,如读取、写入、复制、移动、删除文件等

      • Files.readAllBytes(Path path): 读取指定路径的文件到字节数组中。
      • Files.delete(Path path): 删除文件或目录。
      • Files.exists(Path path, LinkOption... options): 检查文件或目录是否存在。
      • Files.createDirectories(Path dir, FileAttribute<?>... attrs): 创建目录,包括所有不存在的父目录。
    • Paths 是一个工具类,用于创建和操作 Path 对象,是 java.nio.file 包中的一个重要类,表示文件系统中的一个路径

      • Paths 类中常用的方法是 Paths.get(URI uri) 它们用于创建 Path 对象;Path 对象提供了很多方法来操作和查询路径
      Path path = Paths.get("/home/user/file.txt");
      
      • Path.getFileName(): 获取路径中的文件名。

      • Path.getParent(): 获取路径的父目录。

      • Path.toFile(): 将路径转换为 File 对象(注意,File 类是 java.io 包中的类)。

第7集 课程作业之Java实现文件的拷贝案例实战《下》

简介: 课程作业之Java实现文件的拷贝案例实战

  • 具体实现

    package chapter11;
    import java.io.*;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class FileCopier {
    
        public static void copyFile(String sourceFilePath, String targetFilePath) throws IOException {
            // 检查目标文件路径中的目录是否存在
            Path targetPath = Paths.get(targetFilePath).getParent();
            if (targetPath != null && !Files.exists(targetPath)) {
                // 如果目录不存在,则创建它
                Files.createDirectories(targetPath);
            }
    
            try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFilePath));
                 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetFilePath))) {
    
                byte[] buffer = new byte[1024];
                int bytesRead;
    
                while ((bytesRead = bis.read(buffer)) != -1) {
                    bos.write(buffer, 0, bytesRead);
                }
    
                // 注意:由于使用了try-with-resources,流在这里会被自动关闭
            }
        }
    
        public static void main(String[] args) {
            // 示例源文件和目标文件路径
            String sourceFilePath = "/Users/xdclass/Desktop/coding/xdclass-account/src/chapter11/a.txt";
            String targetFilePath = "/Users/xdclass/Desktop/coding/xdclass-account/src/chapter11/a/target/file.txt";
    
            // 调用copyFile方法
            try {
                copyFile(sourceFilePath, targetFilePath);
                System.out.println("文件拷贝成功!");
            } catch (IOException e) {
                System.err.println("文件拷贝失败:" + e.getMessage());
            }
        }
    }
    
    • 思路流程
      • 使用FileInputStreamFileOutputStream分别创建源文件和目标文件的输入/输出流。
      • FileInputStream包装在BufferedInputStream中,以便使用缓冲来提高读取效率。
      • FileOutputStream包装在BufferedOutputStream中,以便使用缓冲来提高写入效率。
      • 使用循环和readwrite方法来从源文件读取数据并写入目标文件,直到没有更多数据可读为止。
      • finally块中(或使用try-with-resources)确保关闭所有打开的流