一天一个javase知识点----IO流

发布于:2025-04-06 ⋅ 阅读:(24) ⋅ 点赞:(0)

IO流

作用:用于读写数据的(可以读写文件,或网络中的数据…)

认识IO流

  • 字节输入流 InputStream(读字节数据的)
  • 字节输出流 OutputStream(写字节数据出去的)
  • 字符输入流 Reader(读字符数据的)
  • 字符输出流 Writer(写字符数据出去的)

字节流

文件字节输入流(FileInputStream)

作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去

a.txt文件

Plain Text
abcabc你好abc号

Java
//1.获取文件的字节输入流对象
InputStream in = new FileInputStream("module\\a3.txt");

//2.读取字节数据(一个一个字节读取)
/*int r = in.read();
System.out.println((char)r);//a

r = in.read();
System.out.println((char)r);//b

r = in.read();
System.out.println((char)r);//c

r = in.read();
System.out.println((char)r);//肯定不是“你”

byte[] bytes =  new byte[3];
r = in.read(bytes);//r是读取的字节数组里面存储数据的长度
System.out.println("字节数组读取数据的长度:"+r);//3
System.out.println("读到的数据:"+new String(bytes));//你*/

byte[] bytes = new byte[3];
int len = -1;// 因为读取文件读不到数据就会返回读取长度为-1

while ((len = in.read(bytes))!=-1){
    //不能直接打印bytes,有可能bytes残留上一次读取的字节数据,所以本次读取多少长度就转换为字符串对应的长度
    //读取len长度,将从0位置开始+读取的字节长度转化为字符串输出
    System.out.println("读到的数据:"+new String(bytes,0,len));
}

//3.释放资源
in.close();

注意事项:

  • 使用FilelnputStream每次读取一个字节,读取性能较差,并且读取汉字输出会乱码。
  • 使用FilelnputStream每次读取多个字节,读取性能得到了提升,但读取汉字输出还是会乱码。

解决方法:

  • 定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。

一次性读完全部字节(不推荐)

注意事项

  • 如果文件过大,创建的字节数组也会过大,可能引起内存溢出

读取文本适合用字符流;字节流适合做数据的转移,比如:文件复制

文件字节输出流(FileOutputStream)

作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去。

Java
// 1.获取文件的字节输出流对象
//OutputStream out = new FileOutputStream(new File("module/a5.txt"));//覆盖方式
OutputStream out = new FileOutputStream(new File("module/a5.txt"), true);//追加方式

// 2.输出数据
// 一个字节
out.write('a');
out.write(97);

// 字符串=》字节数组
out.write("你好".getBytes());
// 字节数组
byte[] bytes = {97, 98, 99};
out.write(bytes);

// 写入换行
out.write('\n');
out.write("\r\n".getBytes());//这个也是换行,推荐使用,windows换行推荐使用\r\n
out.write('a');

// 部分输出
out.write("你好abc".getBytes(), 0, 6);//从0的位置,输出6个字节长度

// 3.释放资源
out.close();

  • 实现写数据的追加操作:创建FileOutpuStream的对象,在构造器第二个参数声明true.
  • 字节输出流实现写出去的数据可以换行:\r\n.

文件复制

  • 任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!

Java
//1.获取文件的字节输入流对象
InputStream in = new FileInputStream("源文件地址");

//2.获取文件的字节输出流对象
OutputStream out = new FileOutputStream("新文件地址");

//3.输入流读取数据输出给输出流
byte[] bytes = new byte[1024];
int len = 0;//定义读取字节长度
while((len = in.read(bytes))!=-1){
    out.write(bytes,0,len);
}

//4.释放资源
out.close();
in.close();
System.out.println("复制完成!");

资源释放问题

try-catch-finally

Java
try{
    ...
    ...
}catch(IOException e){
    e.printStackTrace();
}finally{
    ...
}

  • finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止
  • 作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)

传统方式:

Java
InputStream in = null;
OutputStream out = null;
try {
    // 1.获取文件的字节输入流对象
    in = new FileInputStream("源文件地址");

    // 2.获取文件的字节输出流对象
    out = new FileOutputStream("新文件地址");

    // 3.输入流读取数据输出给输出流
    byte[] bytes = new byte[1024];// 1KB
    int len = 0;// 定义读取字节长度
    while ((len = in.read(bytes)) != -1) {
        out.write(bytes, 0, len);
    }

    System.out.println("复制完成");
    // int a = 1/0;
} catch (IOException e) {
    e.printStackTrace();// 给开发人员看
    System.out.println("文件复制失败,请重启电脑再试...");
} finally {
    System.out.println("finally运行了");
    // finally无论try里面的代码是否出现异常,finally里面的代码都会运行
    try {
        if (in != null) in.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        if (out != null) out.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

try-catch:

  • try-with-resources释放资源,从jdk7开始有的语法
  • 原理:try和catch运行完会自动调用资源的close方法(AutoCloseable接口的方法,每个资源都实现了这个接口重写了close方法)

Java
//try-with-resources释放资源,从jdk7开始有的语法
// 原理:try和catch运行完会自动调用资源的close方法(AutoCloseable接口的方法,每个资源都实现了这个接口重写了close方法)
// 优点:代码优雅,释放资源失败会抛出异常
try (
        // 1.获取文件的字节输入流对象
        InputStream in = new FileInputStream("源文件地址");
        // 2.获取文件的字节输出流对象
        OutputStream out = new FileOutputStream("新文件地址");
        // 定义自己的资源,也会自动调用close方法
        MyResource mr = new MyResource();
){
    //3.输入流读取数据输出给输出流
    byte[] bytes = new byte[1024];//1KB
    int len = 0;//定义读取字节的长度
    while((len = in.read(bytes))!=-1){
        out.write(bytes,0,len);
    }
    System.out.println("复制完成");
}catch(Exception e){
    e.printStackTrace();
    System.out.println("失败,请重启电脑");
}

字符流

文件字符输入流(FileReader)

作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去。

Java
try (//1.获取文件的字符输入流对象
     Reader in = new FileReader(new File("module\\a5.txt"))
      ){
    //2.读取字符数据并打印
    // int c = in.read();
    // System.out.println((char)c);//a
    //
    // c = in.read();
    // System.out.println((char)c);//b
    //
    // c = in.read();
    // System.out.println((char)c);//c

    char[] chars = new char[10];
    int len = -1;//定义读取字符数组的长度,如果读取不到返回-1
    while((len = in.read(chars))!=-1) {
        //将读取到的数据输出,注意读取多少长度,输出多少长度,否则会有残留数据输出
        System.out.print(new String(chars, 0, len));//注意不要换行,因为文本文件里面有换行

    }
}//3.释放资源
catch (Exception e){
    e.printStackTrace();
}

文件字符输出流(FileWriter)

作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去。

Java
try (// 1.获取文件的字符输出流对象
     Writer out = new FileWriter(new File("module/a6.txt"));
) {
     //2.使用字符输出流写数据
    out.write(97);//a
    out.write("你好");//你好
    out.write("帅气das", 0, 2);//帅气

    out.write(new char[]{97,'b','c','尚'});
    System.out.println("\r\n");
    out.write(new char[]{97,'b','c','尚'}, 0, 2);//ab

    out.flush();//刷新流,确保数据写入到文件(输出流可能有部分数据缓存没有写入文件,这样确保缓存数据也写入文件)

    //刷新后继续可以输出数据,注意流关闭后不能再使用流
    out.write("\nhello world");

    //注意:close方法会先调用flush刷新方法,再进行关闭

} catch (Exception e) {
    throw new RuntimeException(e);
}

字符输出输出流的注意实现:字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效。

缓冲流

缓冲字节流

BufferedInputStream缓冲字节输入流BufferedInputStream缓冲字节输入流

  • 作用:可以提高字节输入流读取数据的性能
  • 原理:缓冲字节输入流自带了8KB缓冲池缓冲字节输出流也自带了8KB缓冲池

Java
try(
        //1.获取文件的字节输入流对象
        FileInputStream in = new FileInputStream("源文件地址");
        //缓冲字节输入流对象
        BufferedInputStream bufferedInputStream = new BufferedInputStream(in);

        //2.获取文件的字节输出流对象
        FileOutputStream out = new FileOutputStream("新文件地址");
        //缓冲字节输出流对象
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out);
){
    // 3.输入流读取数据输出给输出流
    byte[] bytes = new byte[1024];//1KB
    int len = 0;//定义读取字节的长度
    while((len = bufferedInputStream.read(bytes))!=-1){
        bufferedOutputStream.write(bytes,0,len);
    }
    System.out.println("复制完成");

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

缓冲字符流

BufferedReader缓冲字符输入流

作用:自带8K(8192)的字符缓冲池,可以提高字符输入流读取字符数据的性能。

字符缓冲输入流新增的功能:按照行读取字符

BufferedWriter缓冲字符输出流

作用:自带8K的字符缓冲池,可以提高字符输出流写字符数据的性能。

字符缓冲输出流新增的功能:换行

Java
try (
        //1.获取文件的字符输入流对象
        Reader in = new FileReader(new File("day03-file-io\\a4.txt"));
        //缓冲字符输入流对象
        BufferedReader bufferedReader = new BufferedReader(in);
        //2.获取文件的字符输出流对象
        FileWriter out = new FileWriter(new File("day03-file-io\\a5.txt"));
        //缓冲字符输出流对象
        BufferedWriter bufferedWriter = new BufferedWriter(out);
){
    //3.输入流读取数据输出给输出流
    String content = null;
    //bufferedReader.readLine() 只是读取每一行的数据(不包含换行)
    while((content = bufferedReader.readLine())!=null){
        bufferedWriter.write(content);//只是输出数据
        //换行
        bufferedWriter.newLine();//注意:输出的时候手动添加换行
    }

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

性能分析

原始流、缓冲流的性能分析

测试用例:

  • 分别使用原始的字节流,以及字节缓冲流复制一个很大视频。

测试步骤:

  1. 使用低级的字节流按照一个一个字节的形式复制文件。
  1. 使用低级的字节流按照字节数组的形式复制文件。
  1. 使用高级的缓冲字节流按照一个一个字节的形式复制文件。
  1. 使用高级的缓冲字节流按照字节数组的形式复制文件。

Java
private static final String SRC_FILE = "想要复制的文件";

private static final String DEST_FILE = "D:\\Test";
public static void main(String[] args) {
    // 目标:缓冲流,低级流的性能分析。
   // copy01(); // 低级的字节流一个一个字节复制:慢的简直无法忍受,直接淘汰,禁止使用!!
   //  copy02(); // 使用低级的字节流按照字节数组的形式复制文件:  相对较慢!可以接受
   //  copy03();  // 使用高级的缓冲字节流按照一个一个字节的形式复制文件:超级慢,直接淘汰
    copy04(); // 使用高级的缓冲字节流按照一个一个字节数组的形式复制文件:极快,推荐使用。
}

// 使用低级的字节流按照一个一个字节的形式复制文件。
public static void copy01(){
    long start = System.currentTimeMillis(); // 开始时间:返回的是 1970-1-1 0:0:0 走到此刻的总毫秒值。 1s = 1000ms
    try (
            // 1、创建字节输入流管道与源文件接通
            InputStream is = new FileInputStream(SRC_FILE);
            // 2、创建字节输出流管道与目标文件接通
            OutputStream os = new FileOutputStream(DEST_FILE + "1.wmv");
    ){
        // 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");
}

// 使用低级的字节流按照字节数组的形式复制文件。
public static void copy02(){
    long start = System.currentTimeMillis(); // 开始时间:返回的是 1970-1-1 0:0:0 走到此刻的总毫秒值。 1s = 1000ms
    try (
            // 1、创建字节输入流管道与源文件接通
            InputStream is = new FileInputStream(SRC_FILE);
            // 2、创建字节输出流管道与目标文件接通
            OutputStream os = new FileOutputStream(DEST_FILE + "2.wmv");
    ){
        // 3、开始一个一个字节数组的转移数据
        byte[] buffer = new byte[1024*8];
        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");
}

//使用高级的缓冲字节流按照一个一个字节的形式复制文件。
public static void copy03(){
    long start = System.currentTimeMillis(); // 开始时间:返回的是 1970-1-1 0:0:0 走到此刻的总毫秒值。 1s = 1000ms
    try (
            // 1、创建字节输入流管道与源文件接通
            InputStream is = new FileInputStream(SRC_FILE);
            BufferedInputStream bis = new BufferedInputStream(is);
            // 2、创建字节输出流管道与目标文件接通
            OutputStream os = new FileOutputStream(DEST_FILE + "3.wmv");
            BufferedOutputStream 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");
}

//使用高级的缓冲字节流按照字节数组的形式复制文件。
public static void copy04(){
    long start = System.currentTimeMillis(); // 开始时间:返回的是 1970-1-1 0:0:0 走到此刻的总毫秒值。 1s = 1000ms
    try (
            // 1、创建字节输入流管道与源文件接通
            InputStream is = new FileInputStream(SRC_FILE);
            BufferedInputStream bis = new BufferedInputStream(is);
            // 2、创建字节输出流管道与目标文件接通
            OutputStream os = new FileOutputStream(DEST_FILE + "4.wmv");
            BufferedOutputStream bos = new BufferedOutputStream(os);
    ){
        // 3、开始一个一个字节数组的转移数据
        byte[] buffer = new byte[1024 * 256];
        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");
}

因此,建议使用字节缓冲输入流、字节缓冲输出流,结合字节数组的方式,目前来看是性能最优的组合。

其他流

字符输入转换流(InputStreamReader)[重点]

解决不同编码时,字符流读取文本内容乱码的问题。

解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了

Java
//当前文件 Test01.java(utf-8)使用的 指明以GBK码表读取at.txt(GBK)文件不会乱码
try(
        //1.定义字节输入流(原始流,里面的字节没有被破坏)
        InputStream inputStream = new FileInputStream("module\\a6.txt");
        //2.定义字符输入转换流
        InputStreamReader gbk = new InputStreamReader(inputStream,"GBK");
        //3.缓冲流提供性能
        BufferedReader br = new BufferedReader(gbk);

){
    char[] chs = new char[1024];
    int len  = -1;
    while((len = br.read(chs))!=-1){
        System.out.println(new String(chs,0,len));
    }

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

打印流(PrintStream和PrintWriter)

作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。

源码中可以看到里面有缓冲流,所以高效。

例子:97---》a,用打印流就不会转化成a。

PrintStream

Java
try(
        //1.定义输出流(注意不要多态,否则无法使用独有方法)
        PrintStream printStream = new PrintStream("module\\a7.txt");
){
    printStream.println("hello");
    printStream.println(100);
    printStream.println(true);
    printStream.println(3.14);
    printStream.println('a');
}catch(Exception e){
    e.printStackTrace();
}

PrintWriter

Java
try(
        //1.定义输出流(注意不要多态,否则无法使用独有方法)
        PrintWriter printWriter = new PrintWriter("module/a8.txt");
){
    printWriter.println("hello");
    printWriter.println(100);
    printWriter.println(true);
    printWriter.println(3.14);
    printWriter.println('a');

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

PrintStream和PrintWriter的区别

  • 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
  • PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法
  • PrintWriter继承自字符输出流Writer,因此支持写字符数据出去
  • tomcat(web)服务器响应数据就是使用这个PrintWriter输出流。

特殊数据流(通信时用到)[了解即可]

DataOutputStream(数据输出流)

允许把数据和其类型一并写出去

Java
try (
        //1.定义资源
        FileOutputStream fileOutputStream = new FileOutputStream("module\\a9.txt");

        //2.使用特殊数据输出流写出数据
        DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
){
    dataOutputStream.writeByte(100);
    dataOutputStream.writeChar('a');
    dataOutputStream.writeDouble(3.14);
    dataOutputStream.writeUTF("黑马程序员");
}catch(Exception e){
    e.printStackTrace();
}

DataInputStream(数据输入流)

用于读取数据输出流写出去的数据。

Java
 try(
        //1..定义资源
        FileInputStream fileInputStream = new FileInputStream("module\\a9.txt");
        //2.使用特殊数据输入流
        DataInputStream dataInputStream = new DataInputStream(fileInputStream);
        ){
    //注意:一定要按照写的顺序读取
    int a = dataInputStream.readByte();
    char b = dataInputStream.readChar();
    double c = dataInputStream.readDouble();
    String d = dataInputStream.readUTF();

    System.out.println(a);
    System.out.println(b);
    System.out.println(c);
    System.out.println(d);
}catch (Exception e){
    e.printStackTrace();
}

IO框架

  • 框架(Framework)是一个预先写好的代码库或一组工具,旨在简化和加速开发过程
  • 框架的形式:一般是把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去。
  • 封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。

什么是IO框架?

  • 封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。

Java
//commons-io包下的IO流框架的使用
//1.一句代码文件复制
FileUtils.copyFile(
        new File("源文件地址"),
        new File("新文件地址")
);

//2.一句代码文件夹复制
FileUtils.copyDirectory(
        new File("源文件夹地址"),
        new File("新文件夹地址")
);

//3.一句代码删除非空文件夹
FileUtils.deleteDirectory(new File("文件夹地址"));

//4.jdk7提供新的方式一句代码文件复制
Files.copy(Path.of("源文件地址"), Path.of("新文件地址"));

//5.IOUtils
IOUtils.copy(new FileInputStream("源文件地址"), new FileOutputStream("新文件地址"));