Java-IO流

发布于:2025-07-15 ⋅ 阅读:(12) ⋅ 点赞:(0)

目录

1.IO流概述

 2.IO流-字节流

 2.1 字节读取方法

2.2 字节流每次读取多个字节

2.3 一次读取完全部字节

2.4 字节输出流

2.5  字节流复制文件

 2.6 资源释放的方式

2.7 复制文件夹案例

2.8 删除文件夹案例

3.IO流-字符流

3.1 FileReader(文件字符输入流)

3.2 FileWrite(文件字符输出流)

4.IO流-缓冲流

 4.1 BufferedInputStream(字节缓冲输入流)

4.2  BufferedReader(字符缓冲输入流)

4.4 BufferedWriter(字符缓冲输出流)

4.5 出师表案例

4.6 缓冲流,字节流性能分析

5.IO流-转换流

5.1 不同编码读取时会乱码的问题

5.2 字符输入转换流

5.3 字符输出转换流

6.IO流-打印流

7.IO流-数据流

 7.1 数据输出流

7.2 数据输入流

8.IO流-序列化流

8.1 对象序列化

8.2 对象反序列化

9.补充:IO框架


1.IO流概述

IO流的作用?

IO流用来读写文件数据

我们对于IO流的学习分两个步骤:

  1.  先搞清楚IO流的分类、体系
  2. 再挨个学习每个IO流的作用、用法

 2.IO流-字节流

 2.1 字节读取方法

package com.itheima.d2_byte_stream;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class FileInputStreamDemo1 {
    public static void main(String[] args) throws Exception {
        // 目标:掌握文件字节输入流每次读取一个字节的形式。
        // 1、创建一个文件字节输入流管道(对象)与源文件接通。
        //会报一个文件找不到异常提示,所以要把异常抛出
        //InputStream is = new FileInputStream(new File("day09-charset-io\\src\\dlei01.txt"));//完整写法
        //简洁写法,不需要写new File创建对象,在源码中已经帮助我们创建
        InputStream is = new FileInputStream("D:\\IDEAProjects\\javasepromax\\day09-charset-io\\src\\dlei01.txt");

        // 2、public int read() 每次读取一个字节返回,如果没有字节可读了,返回-1
//        int b1 = is.read();
//        //返回字节数
//        System.out.println(b1);
//        //强转为char查看内容
//        System.out.println((char) b1);
//
//        int b2 = is.read();
//        System.out.println((char) b2);
//
//        int b3 = is.read();
//        System.out.println(b3);

        is.close();

        // 3、使用循环改进每次读取一个字节。
        InputStream is2 = new FileInputStream(new File("day09-charset-io\\src\\dlei02.txt"));
        int b; // 定义一个变量用于记住每次读取的字节。
        while ((b = is2.read()) != -1) {
            System.out.print((char) b);
        }

        // 拓展了:每次读取一个字节存在很多问题。
        // 1、无法避免读取汉字输出乱码的问题,会截断汉字的字节。
        // 2、每次读取一个字节,性能极差!

        is2.close();
    }
}

2.2 字节流每次读取多个字节

package com.itheima.d2_byte_stream;

import java.io.FileInputStream;
import java.io.InputStream;

public class FileInputStreamDemo2 {
    public static void main(String[] args) throws Exception {
        // 目标:掌握文件字节输入流每次读取多个字节。
        // 1、创建一个文件字节输入流管道与源文件接通
        // InputStream is = new FileInputStream(new File("day09-charset-io\\src\\dlei03.txt"));
        InputStream is = new FileInputStream("day09-charset-io\\src\\dlei03.txt"); // 简化写法

        // 2、每次读取一个字节数组的字节,会返回读取的字节个数,没有字节可读返回-1:public int read(byte[] buffer)
        byte[] buffer = new byte[3];
        int len = is.read(buffer);
//        String rs = new String(buffer);
//        System.out.println("读取的内容:" + rs);
        System.out.println("读取的内容" + new String(buffer));
        System.out.println("读取了几个字节:" + len);


        // 第一桶水: buffer = [a, b, c]
        // 第二桶水:  buffer = [3, m, c]
        int len2 = is.read(buffer);
        // 规范:读取多少就倒出多少。
        String rs2 = new String(buffer, 0, len2);
        System.out.println("读取了几个字节:" + len2);
        System.out.println("读取的内容:" + rs2);

        // 没有字节可读返回-1
        int len3 = is.read(buffer);
        System.out.println(len3);

        System.out.println("----------------------------------------------------------------");

        // 使用循环改进读取方案。
        InputStream is2 = new FileInputStream("day09-charset-io\\src\\dlei04.txt"); // 简化写法

        byte[] buffer2 = new byte[3];
        int len4; // 用于记录每次读取的字节个数
        while ((len4 = is2.read(buffer2)) != -1){
            String rs3 = new String(buffer2, 0, len4);
            System.out.print(rs3);
        }

        // 拓展:每次读取多个字节的优势和问题
        // 优势:性能得到提升。读取性能较好!
        // 缺点:读取汉字输出依然无法避免乱码!!
        
        //用于关闭输入流,释放与该流关联的系统资源,确保系统资源被正确回收
        is2.close();
    }
}

 对于这段代码,我们需要了解以下几个问题:
1.read(byte[] buffer)的工作机制

  • 缓冲区重用:buffer数组是重复使用的。每次调用read(buffer)时,新读取的字节会覆盖数组中的旧内容,但未被覆盖的部分会保留上次的数据
  • 返回值len:表示本次实际读取的字节数。当文件剩余内容不足数组大小时,len会小于数组长度。可以用作下面new String(buffer,0,len2)的一个参数

2. 为什么需要new String(buffer,0,len2)?

假设文件内容为"abc3m",分两次读取,上面代码中定义byte[ ]  buffer = new byte[3]; 每次读取三个字节的内容。

  • 第一次读取:读取3个字节[a,b,c],此时len2 = 3。
  • 第二次读取:仅剩余2个字节[3,m],此时len2 = 2。
  • 若直接转换为整个数组:new String(buffer)会将数组中的所有内容(包括上次残留的c)转换为字符串,得到“3mc”,导致结果错误。
  • 正确做法:new String(buffer,0,len2)仅转换有效部分[3,m],得到正确结果“3m”。

 3.len2表示需要转换的有效字节数,即实际从文件中读取的字节数量,避免将数组中的残留部分包含进来,这也就是为什么说:读取多少倒出多少

2.3 一次读取完全部字节

package com.itheima.d2_byte_stream;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class FileInputStreamDemo3 {
    public static void main(String[] args) throws Exception {
        // 目标:学会如何使用文件字节输入流一次性读取完文本的全部字节,以此避免读取汉字乱码的问题。
        // 1、创建一个文件字节输入流管道与源文件接通
        InputStream is = new FileInputStream("day09-charset-io\\src\\dlei05.txt");

//        // 2、定义一个字节数组与源文件一模一样大。
        File f = new File("day09-charset-io\\src\\dlei05.txt");
//        long size = f.length();
//        byte[] buffer = new byte[(int) size];
//        System.out.println("文件大小:" + size);

//        // 3、一次性读取
//        int len = is.read(buffer);
//        System.out.println("读取的字节数:" + len);
//
//        String rs = new String(buffer);
//        System.out.println(rs);

        System.out.println("--------------------------------------------------");
        // Java官方提供了API;readAllBytes :一次性读取完文件的全部字节
        //readAllBytes()方法不适合读取大文件,小文件(几十MB以内)更加高效
        //大文件(几百MB以上):必须使用缓冲流分块读取,避免内存溢出
        byte[] buffer = is.readAllBytes();
        String rs = new String(buffer);
        System.out.println(rs);

        is.close();
    }
}

为什么这个语句 byte[] buffer = new byte[(int) size];为什么要用int来强制转换?

在Java中,数组的长度必须是int类型,而File.length()返回的是long类型。这是因为文件大小可能超过int的最大值(2^31-1,约 2GB),但数组索引必须在int范围内。

  • 类型不匹配:File.length()返回long类型,而数组初始化需要int。
  • Java语法限制:数组长度必须是int,即使文件大小不超过int范围也需要显式转换

2.4 字节输出流

package com.itheima.d2_byte_stream;

import java.io.FileOutputStream;
import java.io.OutputStream;

public class FileOutputStreamDemo4 {
    public static void main(String[] args) throws Exception {
        // 目标:文件字节输出流的使用。
        // 1、创建一个文件字节输出流管道与目标文件接通。
        // OutputStream os = new FileOutputStream("day09-charset-io/src/dlei06-out.txt"); // 覆盖管道
        OutputStream os = new FileOutputStream("day09-charset-io/src/dlei06-out.txt", true); // 追加管道

        // 2、开始写字节数据出去。
        // public void write(int a): 每次写出去一个字节 。
        os.write(97);
        os.write('a');
        // os.write('徐'); // 汉字是三个字节 [ooo] 只写出去第一个字节,会乱码!
        os.write("\r\n".getBytes()); // 换行符号

        // public void write(byte[] buffer): 每次写一个字节数组的数据出去。
        byte[] bytes = "abc我爱你中国".getBytes();
        os.write(bytes);
        os.write("\r\n".getBytes()); // 换行符号

        // public void write(byte[] buffer, int pos, int len): 写一个字节数组的一部分出去。
        // 参数一:字节数组
        // 参数二:写出去的第一个字节的索引
        // 参数三:总共写出去多少个字节
        os.write(bytes, 3, 9);
        os.write("\r\n".getBytes()); // 换行符号

        //IO流管道属于系统资源,会占用内存和相应的IO资源
        // 用完必须关闭,以释放占用的系统资源。
        // 为了节省系统资源,提升计算机性能。
        //os.flush();//刷新缓存中的数据到磁盘文件中去
        os.close();//关闭包含了刷新
    }
}

Java文件缓存机制

在Java IO操作中,文件缓存机制是一种通过内存临时存储数据,减少磁盘IO次数以提升性能的技术,当程序进行文件读写时,数据不会直接写入磁盘或从磁盘读取,而是先经过内存缓冲区过渡。

核心原理

写入数据:数据先写入内存缓冲区,当缓冲区满或手动刷新时,才批量写入磁盘。

读取数据:从磁盘读取一批数据到缓冲区,后续读取直接从缓冲区获取。

为什么需要缓存?

  • 磁盘IO速度(约100MB/s)远低于内存操作速度(约 25GB/s)
  • 减少磁盘寻道和数据传输的开销
  • 批量操作比单次操作效率更高

 关键方法与缓存控制

1.flush () 方法

  • 强制将缓冲区数据写入磁盘
  • 适用于需要立即看到写入效果的场景(如日志文件)

2. close () 方法

  • 先执行 flush (),再关闭流释放资源,仅刷新数据,流仍可继续使用
  • 必须调用,否则缓冲区数据可能丢失,刷新并释放资源,流不可再使用

2.5  字节流复制文件

package com.itheima.d2_byte_stream;

import java.io.*;

public class CopyTest5 {
    public static void main(String[] args) {
        try {
            // 目标:使用字节流完成文件的复制。
            // E:\resource\aaa.png  ===> D:\resource\meinv.png
            // 1、创建一个文件字节输入流管道与源文件接通
            InputStream is = new FileInputStream("D:\\resource\\aaa.png");
            // 2、创建一个文件字节输出流管道与目标文件接通。
            OutputStream os = new FileOutputStream("D:\\resource\\meinv.png");
            // 3、准备一个字节数组(一个桶)
            byte[] buffer = new byte[1024]; // 1KB
            // 4、开始转移数据  1024 + 1024 + 3
            int len; // 记录每次读取的字节个数。
            while ((len = is.read(buffer)) != -1) {
                // 5、把读取到的源文件字节,读取多少写出去到目标文件多少
                os.write(buffer, 0, len);
            }
            // 6、释放资源
            os.close();
            is.close();
            System.out.println("复制完成!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 2.6 资源释放的方式

package com.itheima.d3_finally;

public class Test1 {
    public static void main(String[] args) {
        // 目标:认识finally的作用。
        //try后或者catch后,最终一定要跑一次,除非JVM挂了。
        try {
            int c = 10 / 2;
            System.out.println(c);
            // return;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 一定要执行一次,除非干掉JVM!
            System.out.println("=======finally=========");
        }

        System.out.println(add(110, 20));
    }

    public static int add(int a, int b){
        try {
            return a + b;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            // finally中不要return数据,会覆盖前面的return数据
            // return 666;
        }
    }

}
package com.itheima.d3_finally;

import java.io.*;

public class Test2 {
    public static void main(String[] args) {
        InputStream  is = null;
        OutputStream os = null;
        try {
            // System.out.println(10 / 0);
            // 目标:使用try-catch-finally释放资源
            // E:\resource\aaa.png  ===> D:\resource\meinv.png
            // 1、创建一个文件字节输入流管道与源文件接通
            is = new FileInputStream("E:\\resource\\aaa.png");
            // 2、创建一个文件字节输出流管道与目标文件接通。
            os = new FileOutputStream("D:\\resource\\meinv.png");
            // 3、准备一个字节数组(一个桶)
            byte[] buffer = new byte[1024]; // 1KB
            // 4、开始转移数据  1024 + 1024 + 3
            int len; // 记录每次读取的字节个数。
            while ((len = is.read(buffer)) != -1) {
                // 5、把读取到的源文件字节,读取多少写出去到目标文件多少
                os.write(buffer, 0, len);
            }
            System.out.println("复制完成!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源(最终一定会执行)
            try {
                if(os != null) os.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

            try {
                if(is != null) is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

package com.itheima.d3_finally;

import java.io.*;

public class Test3 {
    public static void main(String[] args) {
        //目标:JDK 7 开始资源释放的新方式:try-with-resource
        try (
                // 特点:这里只能放资源,并且程序执行完毕后,自动调用他们的close方法关闭!
                // int age = 12;
                // 1、创建一个文件字节输入流管道与源文件接通
                InputStream is = new FileInputStream("E:\\resource\\aaa.png");
                // 2、创建一个文件字节输出流管道与目标文件接通。
                OutputStream os = new FileOutputStream("D:\\resource\\meinv.png");
                //造一个资源实现AutoCloseable接口,验证是否真的会自动调用close()方法
                MyConnection conn = new MyConnection("数据库1号链接");
                ){
            // 目标:使用try-with-resource释放资源
            // 3、准备一个字节数组(一个桶)
            byte[] buffer = new byte[1024]; // 1KB
            // 4、开始转移数据  1024 + 1024 + 3
            int len; // 记录每次读取的字节个数。
            while ((len = is.read(buffer)) != -1) {
                // 5、把读取到的源文件字节,读取多少写出去到目标文件多少
                os.write(buffer, 0, len);
            }
            System.out.println("复制完成!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//造一个资源
class MyConnection implements AutoCloseable{
    private String name;

    public MyConnection(String name) {
        this.name = name;
    }

    @Override
    public void close() throws Exception {
        System.out.println(name + "被自动关闭了~~");
    }
}

2.7 复制文件夹案例

能够使用递归调用定义方法,复制文件夹。

package work.Test1;

import java.io.*;

public class test1 {
    public static void main(String[] args) throws IOException {
        //创建File对象srcDir,代表源文件夹
        File srcDir = new File("C:\\Users\\Lenovo\\Desktop\\aaa\\day09-字符集、IO流(一)");
        //创建File对象desDir,代表目标文件夹
        File desDir = new File("C:\\Users\\Lenovo\\Desktop\\aaa\\新建文件夹");
        //调用copyDir2Dir方法,传递源文件夹和目标文件夹,完成文件夹的复制
        copyDir2Dir(srcDir,desDir);
        System.out.println("文件夹复制完毕!!!");
    }

    //1.定义复制文件到文件夹的方法copyFile2Dir
    public static void copyFile2Dir(File file,File dir) throws IOException {
        //1.1 创建文件字节输入流FileInputStream类的对象,绑定文件资源
        FileInputStream is = new FileInputStream(file);
        //1.2创建输出流FileOutputStream类的对象,绑定目标文件
        FileOutputStream os = new FileOutputStream(dir);

        //1.3定义一个byte数组,保存每次读取到的字节的内容
        byte[] bytes = new byte[1024 * 10];
        //1.4定义int变量,保存每次读取到的字节数量
        int len;
        //1.5根据目标文件夹和源文件,创建目标文件,确保是在目标文件夹里创建和源文件同名的文件
        //创建一个新的File对象,表示目标文件的路径
        //dir这是一个File对象,表示目标文件夹。
        //file.getName()这是源文件的文件名,此方法会返回源文件的名称部分
        //这段代码的作用就是把目标文件夹和源文件的名称动态组合起来,形成完整的目标文件路径
        File destFile = new File(dir,file.getName());
        //1.6循环读取源文件并写入到目标文件
        while ((len = is.read(bytes)) != -1) {
            os.write(bytes,0,len);
        }
        //1.7 关闭流释放资源
        is.close();
        os.close();
    }

    //2.定义复制文件夹到文件夹的方法copyDir2Dir
    public static void copyDir2Dir(File srcDir, File desDir) throws IOException {
        //2.1 在目标文件夹中创建源文件夹
        File newDir = new File(desDir, srcDir.getName());
        if (!newDir.exists()) {
            newDir.mkdirs();
        }
        //2.2获取文件夹中的所有文件和文件夹对应的File对象数组
        File[] files = srcDir.listFiles();
        //2.3判断,如果File对象数组是null或者没有内容,结束方法
        if (files == null || files.length == 0) {
            return;
        }
        //2.4遍历File对象数组
        for (File file : files) {
            //2.5判断,如果当前File对象是文件,调用copyFile2Dir方法,完成文件夹复制
            if (file.isFile()) {
                copyFile2Dir(file,newDir);
            }else {
                //2.6如果当前File对象是文件夹,递归调用当前方法,完成文件夹的复制
                copyDir2Dir(file,newDir);
            }
        }
    }
}

与上面复制图片到文件夹对比:

当前案例为什么需要 new File(dir, file.getName())

  1. 目标路径不包含文件名
    你虽然指定了目标文件夹(如 D:\destination\),但文件夹路径本身不包含文件名。例如:

    • 源文件是 C:\source\test.txt

    • 目标文件夹是 D:\destination\

    • 最终需要生成的目标文件是 D:\destination\test.txt

  2. 动态生成文件名
    代码需要根据源文件的名称(test.txt),在目标文件夹中创建同名文件。因此必须通过 file.getName() 动态获取文件名,并与目标文件夹组合。

上面案例复制单个文件案例(直接指定完整路径包括文件名称  .png ):

为什么不需要动态生成文件名?

  • 完整路径已硬编码
    在创建 FileOutputStream 时,你已经直接指定了完整的目标路径(D:\resource\meinv.png),包括文件夹路径文件名。因此不需要额外的步骤来组合路径。

总结:

  • 当前案例 :目标路径是文件夹,需要动态补全文件名(否则系统不知道该把文件复制成什么名字)。

  • 上面案例:目标路径已经是完整的文件路径(含文件名),直接写入即可。

2.8 删除文件夹案例

package com.itheima.d8_demo;

import java.io.File;

public class DeleteDirDemo2 {
    public static void main(String[] args) {
        // 目标:删除文件夹。
        deleteDir(new File("D:/resource"));
    }

    public static void deleteDir(File dir){
        // 1、不删除的情况
        if(dir == null || !dir.exists()) return;

        // 2、如果是文件直接删除。
        if(dir.isFile()) {
            dir.delete();
            return;
        }


        File[] files = dir.listFiles();
        // 3、文件夹没有权限拿文件,直接不删除!
        if(files == null){
            return;
        }

        // 4、空文件夹 直接删除
        if(files.length == 0){
            // 空文件夹 直接删除
            // Java提供的File.delete()方法只能删除空文件夹
            dir.delete();
            return;
        }

        // 5、遍历全部一级文件对象,删除他们,再删除文件夹自己。
        for (File file : files) {
            if(file.isFile()){
                file.delete();
            }else {
                deleteDir(file);
            }
        }
        dir.delete(); // 删除自己!
    }
}

下面解释一下这段代码的删除逻辑:

边界条件检查:

  • 如果传入的目录为null或不存在,直接返回
  • 如果传入的是文件而非目录,直接删除并返回

在遍历目录中的子项时:

  • 遇到一个文件,直接调用File.delete()删除文件
  • 遇到一个空文件夹时,直接调用File.delete()删除空文件夹
  • 遇到非空文件夹时,调用自身方法进行递归
  • 递归删除子项后,文件夹变为空
  • 最后用File.delete()删除自身

3.IO流-字符流

字节流:适合复制文件等,不适合读写文本文件

字符流:适合读写文本文件内容

3.1 FileReader(文件字符输入流)

 文件字符输入流的好处是可以避免读汉字的时候乱码

package com.itheima.d1_char_stream;

import java.io.FileReader;
import java.io.Reader;

public class FileReaderDemo1 {
    public static void main(String[] args) throws Exception {
        // 目标:文件字符输入流的使用:每次读取一个字符。
        // 1、创建文件字符输入流管道与源文件接通
        Reader fr = new FileReader("day10-io-code\\src\\dlei01.txt");
        // 2、读取一个字符编号回来,没有字符可读返回-1: public int read()\
//        int c1 = fr.read();
//        System.out.println((char) c1);
//
//        int c2 = fr.read();
//        System.out.println((char) c2);
//
//        int c3 = fr.read();
//        System.out.println(c3);

        // 3、使用循环解决
        int c;
        while ((c = fr.read()) != -1){
            char ch = (char) c;
            System.out.print(ch);
        }
        fr.close();

        // 拓展:
        //    是否解决了乱码问题:解决了!
        //    性能较差,依然是个垃圾代码!

    }
}

上面这段代码性能并不好,我们如何一次读多个字符呢?用字符数组来读取文本内容

package com.itheima.d1_char_stream;

import java.io.FileReader;
import java.io.Reader;

public class FileReaderDemo2 {
    public static void main(String[] args) {
        // 目标:文件字符输入流的使用:每次读取多个字符。
        try (
                // 1、创建文件字符输入流管道与源文件接通
                //注意:字符输入流读的就是字符,并不是字节
                Reader fr = new FileReader("day10-io-code\\src\\dlei01.txt");
                ){
            // 2、定义一个字符数组用于读取多个字符
            char[] buffer = new char[1024];
            int len; // 记住每次读取了多少个字符
            while ((len = fr.read(buffer)) != -1){
                String rs = new String(buffer, 0, len);
                System.out.print(rs);
            }

            // 拓展:
            //     可以避免乱码吗? 可以
            //     性能可以吗? 可以
            //     这是截至此刻我们学过的读取文本内容最好的方案。

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

3.2 FileWrite(文件字符输出流)

package com.itheima.d1_char_stream;

import java.io.FileWriter;

public class FileWriteDemo3 {
    public static void main(String[] args) {
        try (
                // 目标:掌握文件字符输出流的使用。
                // 1、创建一个文件字符输出流管道与目标文件接通
                // Writer fw = new FileWriter("day10-io-code/src/dlei02-out.txt"); // 覆盖
                FileWriter fw = new FileWriter("day10-io-code/src/dlei02-out.txt", true); // 追加
        ) {
            // 2、写字符数据出去。
            // a.写一个字符出去 : public void write(int c)
            fw.write(97);
            fw.write('磊');
            fw.write("\r\n"); // 换行

            // b.写一个字符串出去:public void write(String s)
            fw.write("abc我爱你中国666");
            fw.write("\r\n"); // 换行

            // c、写字符串的一部分出去 : public void write(String s, int pos ,int len)
            fw.write("abc我爱你中国666",3, 3);
            fw.write("\r\n"); // 换行

            // d.写一个字符数组出去:public void write(char[] chars)
            //转换为字符数组用的是toCharArray(),而字节数组用的是getByte()
            char[] chars = "abc我爱你中国666".toCharArray();
            fw.write(chars);
            fw.write("\r\n"); // 换行

            // e、写一个字符数组的一部分出去:public void write(char[] chars, int pos ,int len)
            fw.write(chars, 6, 2);
            fw.write("\r\n"); // 换行

//            fw.flush(); // 刷新
//            fw.close(); // 关闭包含刷新

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

在Java代码里使用\r\n进行换行,是和不同操作系统的文本文件换行符规范相关的,是为了不同平台之间的兼容性。

4.IO流-缓冲流

 缓冲流的作用是对原始流进行包装,以提高原始流读写数据的性能

 4.1 BufferedInputStream(字节缓冲输入流)

package com.itheima.d2_buffer_stream;


import java.io.*;

public class BufferedInputStreamDemo1 {
    public static void main(String[] args) {
        // 目标:使用字节缓冲流提升原始字节流读写数据的性能的。
        try (
                // 1、创建字节输入流管道与源文件接通
                InputStream is = new FileInputStream("E:\\resource\\meinv2.jpg");
                // 使用高级的缓冲字节输入流包装低级的字节输入流
                InputStream bis = new BufferedInputStream(is);//多态

                // 2、创建一个字节输出流管道与目标文件接通
                OutputStream os = new FileOutputStream("E:\\resource\\meinv2-bak2.txt");
                // 使用高级的缓存字节输出流包装低级的字节输出流
                OutputStream bos = new BufferedOutputStream(os);//多态

        ) {

            // 3、准备一个字节数组
            byte[] buffer = new byte[1024];
            // 4、转移数据
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.2  BufferedReader(字符缓冲输入流)

4.4 BufferedWriter(字符缓冲输出流)

package com.itheima.d2_buffer_stream;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;

public class BufferedWriterDemo3 {
    public static void main(String[] args) {
        try (
                // 目标:掌握缓冲字符输出流的使用。
                // 1、创建一个文件字符输出流管道与目标文件接通
                Writer fw = new FileWriter("day10-io-code/src/dlei05-out.txt"); // 覆盖
                BufferedWriter bw = new BufferedWriter(fw);
        ) {
            // 2、写字符数据出去。
            // a.写一个字符出去 : public void write(int c)
            bw.write(97);
            bw.write('磊');
            bw.newLine(); // 换行,与bw.write("\r\n")作用相同

            // b.写一个字符串出去:public void write(String s)
            bw.write("abc我爱你中国666");
            bw.newLine(); // 换行

            // c、写字符串的一部分出去 : public void write(String s, int pos ,int len)
            bw.write("abc我爱你中国666",3, 3);
            bw.newLine(); // 换行

            // d.写一个字符数组出去:public void write(char[] chars)
            char[] chars = "abc我爱你中国666".toCharArray();
            bw.write(chars);
            bw.newLine(); // 换行

            // e、写一个字符数组的一部分出去:public void write(char[] chars, int pos ,int len)
            bw.write(chars, 6, 2);
            bw.newLine(); // 换行
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

4.5 出师表案例

3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。

package com.itheima.d2_buffer_stream;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Test4 {
    public static void main(String[] args) {
        // 目标:完成出师表案例。

        try (
                // 2、创建缓冲字符输入流与源文件接通
                BufferedReader br = new BufferedReader(new FileReader("day10-io-code\\src\\csb.txt"));
                // 6、创建缓冲字符输出流与目标文件接通
                BufferedWriter bw = new BufferedWriter(new FileWriter("day10-io-code\\src\\csb-new.txt"));
        ){
            // 1、准备一个集合,存储原文每段落。
            List<String> data = new ArrayList<>();

            // 3、开始按照行读取,并存入到集合中去。
            String line;
            while ((line = br.readLine()) != null){
                data.add(line);
            }
            System.out.println(data);

            // 4、对每段按照首字符编号进行排序
            //排序是默认按照首字符的编号进行排序的
            Collections.sort(data);
            System.out.println(data);

            // 5、把排序好的集合中的每段文章写出到新文件中去,每行都要换行。
            for (String ln : data) {
                bw.write(ln);
                bw.newLine();
            }

            System.out.println("完成了!");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 这段代码执行完成之后会把排序好的出师表输出到一个名称为csb-new.txt文件当中去。

4.6 缓冲流,字节流性能分析

package com.itheima.d2_buffer_stream;

import java.io.*;

public class TimeTest5 {
    public static final String SRC_VIDEO = "D:\\【黑马】2024年11月AI版Java全栈开发V15课程\\02阶段:java基础进阶\\day02-面向对象进阶(二)\\视频\\11、接口的综合案例.mp4";
    public static final String DEST_PATH = "C:\\Users\\Lenovo\\Desktop\\aaa\\";

    public static void main(String[] args) {
        // 目标:原始流和缓冲流的性能分析。
        // copy01(); // 使用低级的字节流按照一个一个字节的形式复制文件: 简直慢的让人无法忍受,直接淘汰,禁止使用!
        copy02(); // 使用低级的字节流按照字节数组的形式复制文件:还可以,但是相对较慢!把桶加大性能就变好了
                  // 性能不一定就慢,把桶加大性能就变好了,但是桶加大到一定程度后影响就微乎其微了。适中就好!
        // copy03(); // 使用高级的缓冲字节流按照一个一个字节的形式复制文件:还是特别慢,不推荐使用!
        copy04(); // 使用高级的缓冲字节流按照字节数组的形式复制文件:极快!推荐使用!

    }

    // 4、使用高级的缓冲字节流按照字节数组的形式复制文件。
    public static void copy04(){
        long start = System.currentTimeMillis();
        try (
                // 1、创建字节输入流管道与源文件接通
                InputStream is = new FileInputStream(SRC_VIDEO);
                InputStream bis = new BufferedInputStream(is);
                // 2、创建一个字节输出流管道与目标文件接通
                OutputStream os = new FileOutputStream(DEST_PATH + "04.avi");
                OutputStream bos = new BufferedOutputStream(os);
        ) {
            // 3、转移数据
            byte[] buffer = new byte[32*1024]; // 1KB
            int len;
            while ((len = bis.read(buffer)) != -1){
                bos.write(buffer,0,len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("使用高级的缓冲字节流按照字节数组的形式复制文件:" +  (end - start) / 1000.0 + "s");
    }

    // 3、使用高级的缓冲字节流按照一个一个字节的形式复制文件。
    public static void copy03(){
        long start = System.currentTimeMillis();
        try (
                // 1、创建字节输入流管道与源文件接通
                InputStream is = new FileInputStream(SRC_VIDEO);
                InputStream bis = new BufferedInputStream(is);
                // 2、创建一个字节输出流管道与目标文件接通
                OutputStream os = new FileOutputStream(DEST_PATH + "03.avi");
                OutputStream bos = new BufferedOutputStream(os);
        ) {
            // 3、转移数据
            int b;
            while ((b = bis.read()) != -1){
                bos.write(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("使用高级的缓冲字节流按照一个一个字节的形式复制文件:" +  (end - start) / 1000.0 + "s");
    }

    // 2、使用低级的字节流按照字节数组的形式复制文件。
    public static void copy02(){
        long start = System.currentTimeMillis();
        try (
                // 1、创建字节输入流管道与源文件接通
                InputStream is = new FileInputStream(SRC_VIDEO);
                // 2、创建一个字节输出流管道与目标文件接通
                OutputStream os = new FileOutputStream(DEST_PATH + "02.avi");
        ) {
            // 3、转移数据
            byte[] buffer = new byte[32*1024];
            int len;
            while ((len = is.read(buffer)) != -1){
                os.write(buffer,0,len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("使用低级的字节流按照字节数组的形式复制文件:" +  (end - start) / 1000.0 + "s");
    }

    // 1、使用低级的字节流按照一个一个字节的形式复制文件。
    public static void copy01(){
        long start = System.currentTimeMillis();
        try (
                // 1、创建字节输入流管道与源文件接通
                InputStream is = new FileInputStream(SRC_VIDEO);
                // 2、创建一个字节输出流管道与目标文件接通
                OutputStream os = new FileOutputStream(DEST_PATH + "01.avi");
        ) {
            // 3、转移数据
            int b;
            while ((b = is.read()) != -1){
                os.write(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("使用低级的字节流按照一个一个字节的形式复制文件耗时:" +  (end - start) / 1000.0 + "s");
    }
}

5.IO流-转换流

5.1 不同编码读取时会乱码的问题

 如果用代码编码UTF-8读取文本文件中GBK的编码文件就会出现乱码的问题。

package com.itheima.d3_transfer_stream;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;

public class Test1 {
    public static void main(String[] args) {
        // 目标:不同编码下,字符流读取文本内容的问题。
        try (
                // 代码编码:UTF-8   文件编码:UTF-8   a我m ==> o [ooo] o  不乱码
                // 代码编码:UTF-8   文件编码:GBK     a我m ==> o [oo] o   乱码
                // 1、创建一个文件字符输入流与源文件接通。
                Reader fr = new FileReader("E:\\resource\\abc.txt");
                // 把原始的字符输入流包装成高级的缓冲字符输入流
                BufferedReader br = new BufferedReader(fr);
        ){
            String line; // 记住每次读取的一行数据。
            while ((line = br.readLine()) != null){
                System.out.println(line);
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

5.2 字符输入转换流

package com.itheima.d3_transfer_stream;

import java.io.*;

public class InputStreamReaderDemo2 {
    public static void main(String[] args) {
        // 目标:字符输入转换流。
        try (
                // 1、得到GBK文件的原始字节输入流(GBK文件中文有两个字节)
                InputStream is = new FileInputStream("E:\\resource\\abc.txt");
                // 2、通过字符输入转换流把原始字节流按照指定编码转换成字符输入流
                Reader isr = new InputStreamReader(is, "GBK");
                // 3、把字符输入流包装成高级的缓冲字符输入流。
                BufferedReader br = new BufferedReader(isr);
                ){
            // 4、按照行读取
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

5.3 字符输出转换流

package com.itheima.d3_transfer_stream;

import java.io.*;

public class OutputStreamWriterDemo3 {
    public static void main(String[] args) {
        try (
                // 目标:掌握字符输出转换流:可以指定写出去的字符编码。
                // 1、创建一个文件字节输出流通向目标文件。
                OutputStream os = new FileOutputStream("day10-io-code/src/dlei06-out.txt");
                // 指定写出去的编码是GBK
                Writer osw = new OutputStreamWriter(os, "GBK");
                // 2、把字符输出流包装成缓冲输出流
                BufferedWriter bw = new BufferedWriter(osw);
                ){

            bw.write("莫道桑榆晚");
            bw.newLine();
            bw.write("为霞尚满天");
            bw.newLine();
            bw.write("abc666");
            bw.newLine();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

实际开发中,可以通过字节转换的方式实现GBK编码转换,可以直接操作字节数组:
核心代码:

String text = "字节编码测试";
byte[] gbkBytes = text.getBytes("GBK"); //字符串->GBK字节数组
String text = new String(gbkBytes,"GBK");//GBK字节数组->字符串
fos.write(gbkBytes);//直接写入字节数组

6.IO流-打印流

package com.itheima.d4_printstream;

import java.io.PrintStream;

public class PrintStreamDemo1 {
    public static void main(String[] args) {
        try (
                // 目标:打印流:方便,高效的写数据出去。
                PrintStream ps = new PrintStream("day10-io-code/src/ps.txt");//只能覆盖
                // PrintWriter ps = new PrintWriter("day10-io-code/src/ps.txt");
                //要追加的话就要包原来的低级管道
//                PrintWriter ps = new PrintWriter(new FileWriter("day10-io-code/src/ps.txt", true));
                ){
            ps.println(666);
            ps.println(97);
            ps.println(97.9);
            ps.println('a');
            ps.println(true);
            ps.println("我爱学Java");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 打印流的一种应用:输出语句的重定向,把输出在控制台的语句修改到文档中输出,用System.setOut( )

package com.itheima.d4_printstream;

import java.io.FileOutputStream;
import java.io.PrintStream;

public class PrintStreamDemo2 {
    public static void main(String[] args) throws Exception {
        // 目标:输出语句的重定向。
        System.out.println("红豆生南国");
        System.out.println("春来发几枝");

        PrintStream ps = new PrintStream(new FileOutputStream("day10-io-code/src/ps2.txt", true));
        System.setOut(ps); // 把系统类的打印流改成我的打印流!!

        System.out.println("愿君多采撷");
        System.out.println("此物最相思");
    }
}

7.IO流-数据流

 7.1 数据输出流

package com.itheima.d5_dataOutputStream;

import java.io.DataOutputStream;
import java.io.FileOutputStream;

public class DataOutputStreamDemo1 {
    public static void main(String[] args) {
        try (
                // 目标:掌握数据输出流写数据出去的特点:可以写数据和其类型,而且后面可以读取出来。
                DataOutputStream dos = new DataOutputStream(new FileOutputStream("day10-io-code/src/data.txt"));
                ){

            // 2、写数据和其类型出去
            dos.writeByte(97);
            dos.writeBoolean(true);
            dos.writeInt(1000);
            dos.writeChar('a');
            dos.writeUTF("我在北京天安门666");

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

7.2 数据输入流

package com.itheima.d5_dataOutputStream;

import java.io.DataInputStream;
import java.io.FileInputStream;

public class DataInputStreamDemo2 {
    public static void main(String[] args) {
        // 目标:掌握特殊数据输入流读取特殊数据输出流写出去的数据。
        try (
             // 1、创建高级的特殊数据输入流管道包装低级的字节输入流管道。
             DataInputStream dis = new DataInputStream(new FileInputStream("day10-io-code/src/data.txt"));
        ){
            // 2、开始读取
            byte b = dis.readByte();
            System.out.println(b);

            boolean b1 = dis.readBoolean();
            System.out.println(b1);

            int i = dis.readInt();
            System.out.println(i);

            char c = dis.readChar();
            System.out.println(c);

            String s = dis.readUTF();
            System.out.println(s);


        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

8.IO流-序列化流

8.1 对象序列化

对象序列化:把Java对象写入到文件中去

如果有一些对象不希望被序序列化,例如Student中的passWord,那么我们可以用transient。transient修饰的成员变量将不参与序列化。

package com.itheima.d6_object;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
// 注意:如果学生对象要参与序列化,必须实现序列化接口
public class Student implements Serializable {
    private String name;
    private int age;
    // transient 修饰的成员变量将不参与序列化
    private transient String passWord;
    private double height;
}
package com.itheima.d6_object;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class ObjectOutputStreamDemo1 {
    public static void main(String[] args) throws Exception {
        // 目标:完成对象的序列化:把Java对象存储到文件中去
        // 1、准备一个对象。
        Student s = new Student("风驴子", 27, "mazi666", 160);
        // 2、创建对象字节输出流管道与目标文件接通
        ObjectOutputStream oos =
                new ObjectOutputStream(new FileOutputStream("day10-io-code/src/obj.txt"));
        // 3、开始写对象出去
        oos.writeObject(s);
        // 4、关闭资源
        oos.close();
    }
}

 序列化之后的文件中不是乱码,是对象序列化特殊的存储方式

8.2 对象反序列化

对象反序列化:把文件里的Java对象读出来

package com.itheima.d6_object;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class ObjectInputStreamDemo2 {
    public static void main(String[] args) throws Exception {
        // 目标:对象反序列化 使用对象字节输入流管道把文件中的Java对象又读入到内存中来。
        // 1、创建对象字节输入流管道
        ObjectInputStream ois =
                new ObjectInputStream(new FileInputStream("day10-io-code\\src\\obj.txt"));

        // 2、读取对象进来(对象反序列化)
        // 实际上Java读取的对象都是Object类型的,因为Java无法判断文件中的具体类型
        // 但是我们可以将对象强转为实际类型
        Student s = (Student) ois.readObject();

        System.out.println(s);

        ois.close();
    }
}

如何如果要一次序列化多个对象?

用一个ArrayList集合存储多个对象,然后直接对集合进行序列化即可

注意:ArrayList集合已经实现了序列化接口!

9.补充:IO框架

package com.itheima.d7_commons_io;


import java.nio.file.Files;
import java.nio.file.Path;

/**
 * 目标:使用CommonsIO框架进行IO相关的操作。
 */
public class CommonsIOTest1 {
    public static void main(String[] args) throws Exception {
//           FileUtils.copyFile(new File("day10-io-code/src/dlei04.txt"), new File("day10-io-code/src/dlei04-new.txt"));
//           FileUtils.copyDirectory(new File("E:\\resource"), new File("E:\\resource-dlei-bak"));
//           FileUtils.deleteDirectory(new File("E:\\resource-dlei-bak"));

        // JDK 7开始也新增了单行复制相关的计数。
        Files.copy(Path.of("day10-io-code/src/dlei04.txt"), Path.of("day10-io-code/src/dlei04-dlei-666.txt"));
        //Files.delete(Path.of("day10-io-code/src/dlei04-new.txt"));
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到