多线程和IO流【java全端课】

64 阅读11分钟

一、IO流

1.1 File类

1.1.1 File类对象的含义

java.io.File类是文件和目录路径名的抽象表示形式。

  • File类的对象是用于表示一个文件或文件夹
  • 想要表示一个文件或文件夹,需要通过描述它的路径名来创建它的对象。例如:d:\1.txt
  • File类的对象代表的文件或文件夹不一定真实存在

1.1.2 File类的常用方法

1、创建、删除、重命名
package com.mytest.io;

import org.junit.Test;
import java.io.File;
import java.io.IOException;

public class TestFile {
    @Test
    public void test1(){
        File f = new File("d:\\1.txt");
        //这里的f对象,想要代表 d:\1.txt 这个文件
        //这个文件此时不存在,我们可以将它创建出来
        try {
            f.createNewFile();//在磁盘空间对应位置创建一个文件
            //创建这个文件可能失败(1)没有D盘(2)没有权限(3)磁盘空间已满 ....
            //如果创建不成功,会报异常
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void test2(){
        File f =new File("d:\\test");//代表一个文件夹
        f.mkdir();//在磁盘空间对应位置创建一个文件夹
        //如果创建不成功,不会报异常
    }

    @Test
    public void test3(){
        File f = new File("d:\\1.txt");
        //从磁盘空间对应位置删除这个文件
        f.delete();
    }

    @Test
    public void test4(){
        File f =new File("d:\\test");
        //从磁盘空间对应位置删除这个文件夹
        f.delete();//空文件夹可以删除
    }

    @Test
    public void test6(){
        File f =new File("d:\\temp");
        //从磁盘空间对应位置删除这个文件夹
        f.delete();//非空文件夹不可以这样删除
    }

    @Test
    public void test7(){
        File f =new File("d:\\test");
        File f2 = new File("d:\\测试");
        f.renameTo(f2);//重命名文件夹名

    }

    @Test
    public void test8(){
        File f = new File("d:\\1.txt");
        File f2 = new File("d:\\2.txt");
        f.renameTo(f2);//重命名文件名
    }
}
2、查看文件或文件夹的信息
package com.mytest.io;

import org.junit.Test;
import java.io.File;
import java.util.Date;

public class TestFile2 {
    @Test
    public void test1(){
        File f = new File("d:\\2.txt");//文件存在

        System.out.println("文件名:" + f.getName());
        System.out.println("文件大小:" + f.length());
        System.out.println("文件的最后修改时间:" + f.lastModified());//距离1970-1-1 8:0:0的毫秒值
        Date d = new Date(f.lastModified());
        System.out.println("文件的最后修改时间:" + d);//Mon Dec 23 11:47:00 CST 2024
        System.out.println("f是一个文件吗?" + f.isFile());
        System.out.println("f是一个文件夹吗?" + f.isDirectory());
        System.out.println("f是隐藏的文件或文件夹吗?" + f.isHidden());
        System.out.println("f是存在的吗?" + f.exists());
        System.out.println("f是可读的吗?" + f.canRead());
    }

    @Test
    public void test2(){
        File f = new File("d:\\temp");//文件夹存在
        System.out.println("文件夹名:" + f.getName());
        System.out.println("文件夹大小:" + f.length());//错误的,无法直接获取文件夹的大小
        System.out.println("文件夹的最后修改时间:" + f.lastModified());//距离1970-1-1 8:0:0的毫秒值
        Date d = new Date(f.lastModified());
        System.out.println("文件夹的最后修改时间:" + d);//Mon Dec 23 11:47:00 CST 2024
        System.out.println("f是一个文件吗?" + f.isFile());
        System.out.println("f是一个文件夹吗?" + f.isDirectory());
        System.out.println("f是隐藏的文件或文件夹吗?" + f.isHidden());
        System.out.println("f是存在的吗?" + f.exists());
        System.out.println("f是可读的吗?" + f.canRead());
    }

    @Test
    public void test3(){
        File f = new File("d:\\temp");//文件夹存在

        String parent = f.getParent();
        System.out.println("上级目录:" + parent);
        //parent.方法  如果要调用方法,只能调用String类的方法,因为parent变量是String类型

        String[] subList = f.list();
        for (String sub : subList) {
            System.out.println(sub);
        }
    }

    @Test
    public void test4(){
        File f = new File("d:\\temp");//文件夹存在

        File parentFile = f.getParentFile();
        //parentFile.方法  如果要调用方法,可以调用File类的方法
        System.out.println(parentFile.isDirectory());//继续调用File类的方法

        File[] files = f.listFiles();
        for (File subFile : files) {
            System.out.println("下一级" + subFile + "是文件吗:" + subFile.isFile());
        }
    }

    @Test
    public void test6(){
        //求temp文件夹的总大小
        File f = new File("d:\\temp");//文件夹存在
//        System.out.println(f.length());//错误,无法直接获取文件夹大小

        long result = totalLength(f);
        System.out.println(result+"字节");
        //753239269字节
    }

    public long totalLength(File f){
        if(f.isFile()){
            return f.length();
        }else if(f.isDirectory()){
            long sum = 0;
            File[] files = f.listFiles();
            for (File sub : files) {
//              sum=  sum + sub的大小;
//                sum =sum + sub.length();//错误,sub可能是文件夹
                sum =sum + totalLength(sub);
            }
            return sum;
        }
        return 0;
    }

    @Test
    public void test7(){
        //思考:如何删除非空文件夹?
        File f = new File("d:\\柴林燕");//文件夹存在
//        f.delete();//无法直接删除非空文件夹
        forceDeleteDirectory(f);
    }

    public void forceDeleteDirectory(File f){
        if(f.isDirectory()){//如果是文件夹,先清空文件夹内部的下一级
            File[] files = f.listFiles();
            for (File sub : files) {
                forceDeleteDirectory(sub);//删除下一级,它可能是文件或文件夹
            }
        }
        f.delete();//删除文件,或删除以及清空下一级的文件夹
    }
}
3、文件或文件夹的路径

用来表示文件或文件夹的路径名表示有多种方式:

  • 绝对路径:
    • Windows操作:以盘符:开头的路径,例如:D:\temp\java\Hello.java
    • Linux或Mac系统:以/开头的路径
  • 相对路径:
    • Windows或Linux或Mac系统:以文件夹名或文件名开头的就是相对路径
    • 相对于谁呢?
      • main方法,相对于当前的Project
      • @Test方法,相对于当前的Module
  • 非规范路径:包含../ 等描述符的路径
  • 规范路径:不包含../ 等描述符的路径
public void test(){
    //它俩都是相对路径,因为它们不是以盘符开头(Windows操作系统)
    //因为是@Test方法,相对于当前的module
    File f1 = new File("../../test1.txt");//非规范路径
    File f2 = new File("test2.txt");//规范路径

    System.out.println("f1构造路径(构造器实参列表中写的路径):" + f1.getPath());
    System.out.println("f2构造路径(构造器实参列表中写的路径):" + f2.getPath());

    System.out.println("f1的绝对路径:" + f1.getAbsolutePath());
    System.out.println("f2的绝对路径:" + f2.getAbsolutePath());

    try {
        System.out.println("f1的规范路径:"  + f1.getCanonicalPath());
        System.out.println("f2的规范路径:"  + f2.getCanonicalPath());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

1.2 IO流类

通过File类只能创建、删除、重命名文件,获取文件的元数据(包括文件名、路径名、大小等)。如果想要操作文件的内容,必须通过IO流类。

提醒:文件夹是没有具体内容。只有文件才能装数据。

1.2.1 方向

  • I:输入 input
  • O:输出 output

1.2.2 方式

  • 字节流:以字节为单位,最小读/写一个字节。可以操作任意类型的文件,包括纯文本文件,图片、音频、视频等。
  • 字符流:以字符为单位,最小读/写一个字符,一个字符占就一个字节要看编码方式(这里不是只JVM中,是说在读或写的过程中,传输的过程中,编码方式有ISO8859-1,GBK,UTF-8等)。只能操作纯文本文件。
    • 什么是纯文本文件?.txt,.java,.class,.css,.js,.html,.sql 等
    • 以下这些不是纯文本文件: .doc,.docx,.ppt,.pptx,.xls,.jpg,.mp3

提醒:当你指定文件的时候,一定要带上后缀名或扩展名。后缀名或扩展名是代表或能体现文件类型的。

1.2.3 四个抽象基类

  • InputStream:字节输入流
    • 子类:FileInputStream,BufferedInputSream,ObjectInputStream等
  • OutputStream:字节输出流
    • 子类:FileOutputStream,BufferedOutputStream,ObjectOutputStream,PrintStream等
  • Reader:字符输入流
    • 子类:FileReader,BufferedReader,InputStreamReader等
  • Writer:字符输出流
    • 子类:FileWriter,BufferedWriter,OutputStreamWriter,PrintWriter等

1.2.4 文件IO流

1、案例:输出一句话到纯文件中
package com.mytest.io;

import org.junit.Test;
import java.io.FileWriter;
import java.io.IOException;

public class TestFileWriter {
    @Test
    public void test1() throws IOException {//暂时先不处理异常,后面单独讲IO流的异常处理问题
        //输出一句话到纯文件中
        //数据流向:程序 -> 文件
        //(1)创建一个输出流,因为这里是操作纯文本文件
        //对于不存在的文件,会自动创建
        FileWriter fw = new FileWriter("java/1.txt");
        //(2)输出输出一句话到纯文件中
        fw.write("test test");
        //(3)关闭IO流
//        fw.flush();//刷新输出流,把数据及时写出去(用flush)
        fw.close(); (最后是close)
    }
    
    // fw.flush(), fw.close() 都会把数据写出去,flush只能中间使用。close 最后使用,close 后不能再写出数据。
}
2、案例:读取一个纯文本文件的内容
package com.mytest.io;

import org.junit.Test;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;

public class TestFileReader {
    @Test
    public void test()throws IOException{
        //此时文件内容:hellojavachar
        FileReader fr = new FileReader("java/1.txt");

        char[] arr = new char[5];
        while(true){
            int len = fr.read(arr);
            if(len == -1){
                break;
            }
            //把char[] 转为 字符串
            System.out.print(new String(arr,0,len));
            //这里(arr,0,len),表示从arr数组中取len个字符构成字符串,从arr[0]开始取
        }

        fr.close();
    }
}
3、案例:用字节流输出一句话到纯文本文件中
package com.mytest.io;

import org.junit.Test;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestFileOutputStream {
    @Test
    public void test1()throws IOException {
        //输出一句话到纯文件中
        //数据流向:程序 -> 文件
        //(1)创建一个输出流,这里是操作纯文本文件,既可以使用FileWriter,也可以使用FileOutputStream
        //建议:FileWriter
        FileOutputStream fos = new FileOutputStream("java/3.txt");
        //(2)输出一句话到纯文件中
        fos.write("疯狂星期四".getBytes());//如果getBytes()参数里面是空的,代表用当前运行环境的编码方式
                                                    //我现在IDEA设置的是UTF-8
        //(3)关闭
        fos.close();
    }
}
4、案例:用字节流读取纯文本文件
package com.mytest.io;

import org.junit.Test;
import java.io.FileInputStream;

public class TestFileInputStream {
    @Test
    public void test1()throws Exception{
        //读取java/1.txt文件的内容到程序中
        //数据流向:文件 -> 程序
        //         程序 -> 控制台
        //(1)创建输入流  可以是FileReader的对象,也可以是FileInputStream
        //建议 FileReader的对象,这里演示FileInputStream
        FileInputStream fis =new FileInputStream("java/1.txt");
        byte[] data = new byte[5];
        while(true){
            int len = fis.read(data);//把读取的内容放到data数组中,并且返回本次读取了几个字节
            if(len == -1){
                break;
            }
            //把byte[]转为字符串
            System.out.println(new String(data,0,len));
        }
        fis.close();
    }
}
5、读取不同编码的问题

结论:

如果是整体读取(例如:复制),那么字节流和字符流都可以。

如果是一边读取,一边显示(构建字符串查看内容),那么字节流是不适合读取纯文本文件的。需要用字符流,因为FileReader和FileWriter在读和写的时候可以指定文件编码。

6、案例:如何读写数字等非纯文本数据
package com.mytest.io;

import org.junit.Test;
import java.io.*;

public class TestNotString {
    @Test
    public void test1()throws Exception{
        /*
        输出以下数据:
        int num = 6;
        double d = 3.14;
        boolean b = true;
        String s = "hello";
        到一个文件  5.aaa
        注意,这里的扩展名是我随便写的,表示不是纯文本。不与已有的扩展名相同即可。
        不能用字符流,只能用字节流。因为字符流只能操作纯文本文件。
         */
        FileOutputStream fos = new FileOutputStream("java\\5.aaa");
        //如果此时只用FileOutputStream,无法实现功能,
        //需要借助其他IO流来帮我们完成目标。可以借助的IO流有:DataOutputStream和ObjectOutputStream
        //它俩有共同的父接口 DataOutput
        //这里介绍 ObjectOutputStream ,因为后续我们输出对象时,也是用它
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        //数据的流向: 程序 -> oos -> fos -> 文件

        int num = 6;
        double d = 3.14;
        boolean b = true;
        String s = "hello";

        oos.writeInt(num);
        oos.writeDouble(d);
        oos.writeBoolean(b);
        oos.writeUTF(s);//用修改版,特定版的UTF-8来输出

        oos.close();
        fos.close();
        //上述写出去的文件内容,用现有的任意类型的软件都打不开。因为它不是纯文本文件,也不是word文档,不是图片,不是视频等。

    }

    @Test
    public void test2()throws Exception{
        //但是可以通过对应的Java程序读取这个文件的内容查看。
        FileInputStream fis = new FileInputStream("java\\5.aaa");
        //如果此时只用FileInputStream,无法实现功能,
        //需要借助其他IO流来帮我们完成目标。可以借助的IO流有:DataInputStream和ObjectInputStream
        ObjectInputStream ois = new ObjectInputStream(fis);

        //数据的流向: 文件 -> fis -> ois -> 程序
        //读的顺序必须与写的顺序、类型一致
        int a = ois.readInt();
        double b = ois.readDouble();
        boolean c = ois.readBoolean();
        String d = ois.readUTF();

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);

        ois.close();
        fis.close();
    }
}

7、案例:提高文件的读写效率

方案一:在读和写的时候,把字节或字符数组定义的大一点。

方案二:借助缓冲流(缓冲流的本质仍然是内部有一个大一点的数组,默认长度是8192)

  • BufferedInputStream可以包装InputStream系列的IO流提高读取效率
  • BufferedOutputStream可以包装OutputStream系列的IO流提高输出效率
  • BufferedReader可以包装Reader系列的IO流提高读取效率
  • BufferedWriter可以包装Writer系列的IO流提高输出效率
package com.mytest.io;

import java.io.*;

public class FileTools {//工具类
    //这个方法可以适用于任意类型的文件的复制,所以只能选择字节流
    public static void copy(String srcFilePath, String destFilePath)throws IOException {
        //从srcFilePath源文件读取内容,写到destFilePath目标文件中
        FileInputStream fis = new FileInputStream(srcFilePath);
        FileOutputStream fos = new FileOutputStream(destFilePath);

        //数据流向:srcFilePath文件 -> fis ->  data数组 -> fos -> destFilePath目标文件
        byte[] data = new byte[10];//这里长度奇偶数都可以,因为咱们是整个文件的复制
        while(true){
            int len = fis.read(data);
            if(len == -1){
                break;
            }
            fos.write(data,0,len);//本次读取了几个字节,就输出几个字节
        }

        fos.close();
        fis.close();
    }

    public static void copyQuick(String srcFilePath, String destFilePath)throws IOException {
        //从srcFilePath源文件读取内容,写到destFilePath目标文件中
        FileInputStream fis = new FileInputStream(srcFilePath);
        FileOutputStream fos = new FileOutputStream(destFilePath);

        //缓冲流的作用是提供效率,了解一下
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        //数据流向:srcFilePath文件 -> fis -> bis ->  data数组 -> bos -> fos -> destFilePath目标文件
        byte[] data = new byte[10];//这里长度奇偶数都可以,因为咱们是整个文件的复制
        while(true){
            int len = bis.read(data);
            if(len == -1){
                break;
            }
            bos.write(data,0,len);//本次读取了几个字节,就输出几个字节
        }

        //倒着关  bos与fos倒着关,bis与fis倒着关,先new的后关
        bos.close();
        fos.close();
        bis.close();
        fis.close();
    }
}

3.2.5 IO流的关闭和异常处理

1、关闭顺序
FileWriter fw = new FileWriter("java\\1.txt");
BufferedWriter bw = new BufferedWriter(fw);

bw.write("test");

fw.close();
bw.close();//错误:java.io.IOException: Stream closed
//数据的流向:程序 -> bw -> fw -> 文件
//(1)先关闭fw,相当于断开了fw与文件的连接
//(2)再关闭bw时,如果bw中还有数据需要写出,那么就此路不通
2、异常处理
try{
    业务代码
}catch(异常类型 e){
    异常处理
}finally{
    资源关闭
}

Java7开始,引入了try-catch-with-resource的新语法糖:

try(资源声明){
    业务代码
}catch(异常类型 e){
    异常处理
}
//只要在try()中声明的资源,就可以自动关闭。
@Test
public void test2(){
    //Java7开始,引入了try-catch-with-resource的新语法糖
    try(
        FileWriter fw = new FileWriter("java\\1.txt");
        BufferedWriter bw = new BufferedWriter(fw);
    ) {

        bw.write("test");
    } catch (IOException e) {
        e.printStackTrace();
    }
}