【文件IO】 文件系统的操作 | 文件类型 | File的构造和方法 | 字节流 | 字符流 | InputStream | OutStream

发布于:2024-04-24 ⋅ 阅读:(23) ⋅ 点赞:(0)

文件IO

一、文件的概念

“文件”是一个广义的概念

​ 在操作系统中,会把很多的 硬件设备 和 软件资源 都抽象成“文件”,同一进行管理。大部分情况下,谈到的文件,都是指硬盘的文件。文件就相当于是针对“硬盘”数据的一种抽象。

在编程的过程中,并不需要知道具体的存储细节,只是根据文件来进行操作

机械硬盘(HDD)

适合顺序读写,不适合随机读写,因为磁头的移动需要花费时间。

固态硬盘(SSD)

  • 往往都是通过文件的方式来操作硬盘。
文件系统

​ 文件是通过”文件系统“来进行组织管理的,是操作系统提供的模块。操作系统使用“目录”(树形结构)来组织文件,“此电脑”就是目录树的根节点。而一个个文件夹,就是目录(directory)。目录可以包含目录或者文件。

文件路径:
  • 使用目录的层次结构来描述文件所在的位置

绝对路径:以盘符开头,到达指定文件。

相对路径:以.或者…开头的。先指定一个目录,作为基准目录,从基准目录出发,到达指定文件。

.当前目录 …当前目录的上一级

如果是命令行进行操作,基准目录就是当前所处的目录

对于IDEA来说,基准目录就是项目目录。

文件类型

从编程的角度出发,文件的类型分为两大类:

1.文本文件:文件中保存的数据都是字符串,保存的内容都是合法的字符

2.二进制文件:文件中保存的数据仅仅是二进制数据,不要求保存的内容合法。

虽然本质上存储的数据都是二进制的,但是区别就是是否能转换成合法的字符。

合法字符:符合字符集/字符编码 utf-8的字符

二、文件操作

Java针对文件的操作,分为两类

1.文件系统的操作:

​ 例如创建文件、删除文件、判定文件、重命名等

这些操作同样可以在Java代码中完成。这些操作依赖了File类,封装了这些操作。

File的构造和方法

pathSeparator属性 :依赖于系统的路径分隔符 :

windows \ 和 / 都可以 ,但是Linux和Mac 上是 “ / ”

一个File对象,就表示了一个硬盘上的文件。在构造对象的时候,需要制定这个文件的路径

    public static void main(String[] args) throws IOException {
        File file = new File("./test.txt");
        //创建文件
        System.out.println(file.getParent());
        //获取当前文件的父目录
        System.out.println(file.getName());
        //获取当前文件名
        System.out.println(file.getPath());
        //获取当前文件路径
        System.out.println(file.getAbsolutePath());
        //获取当前文件的绝对路径  ( 基准目录 + 相对路径)
        System.out.println(file.getCanonicalPath());
        //获取当前文件的修饰过的绝对路径 (简化)    windows上的盘符不区分大小写
    }
.
test.txt
.\test.txt
F:\java\multi-thread\.\test.txt
F:\java\multi-thread\test.txt
创建文件
    public static void main(String[] args) throws IOException {
        File file= new File("e:/test.txt");
        System.out.println(file.exists());
        //判断当前文件是否存在
        System.out.println(file.isDirectory());
        //判断是不是目录
        System.out.println(file.isFile());
        //判断是不是文件

        //创建文件
        boolean ret = file.createNewFile();
        System.out.println("ret= "+ret);
        System.out.println(file.exists());
        System.out.println(file.isDirectory());
        System.out.println(file.isFile());
    }
false
false
false
ret= true
true
false
true
删除文件
    public static void main1(String[] args) {
        File file = new File("e:/test.txt");
        boolean ret = file.delete();
        //删除文件
        System.out.println("ret = "+ret);
    }
等线程结束后删除文件
    public static void main(String[] args) throws InterruptedException, IOException {
        File file = new File("e:/test.txt");
        file.createNewFile();
        file.deleteOnExit();
        //进程结束之后再进行删除
        Thread.sleep(5000);
        System.out.println("进程结束");
    }
列出目录内容
    public static void main(String[] args) {
        File file = new File("e:/");
        String[] files = file.list();
        //System.out.println(files);如果直接打印,打印的是这个数组对应的hashcode
        System.out.println(Arrays.toString(files));
        //打印目录列表
    }
创建目录
    public static void main(String[] args) {
        File file = new File("e:/test111/222/333");
        boolean ret = file.mkdirs();
        System.out.println(ret);
    }
重命名
    public static void main(String[] args) throws IOException, InterruptedException {
        File scrFile = new File("e:/test.txt");
        scrFile.createNewFile();
        Thread.sleep(5000);
        File destFile = new File("e:/test2.txt");
        boolean ret = scrFile.renameTo(destFile);
        System.out.println(ret);
    }
2.文件内容的操作:

利用流对象(Steam)对文件进行操作

像水流一样,可以分成无数种方案,称为文件流

1.字节流:对应二进制文件
InputStream 输入
  • read的返回值是int,实际表示的是byte(0~255之间的数据),使用-1表示读到文件末尾
  • 读取的是内容的ascii码
        try(InputStream inputStream = new FileInputStream("e:/test.txt")){
            byte[] buffer = new byte[1024];
            int n = inputStream.read(buffer);
            System.out.println("n= "+n);
            for (int i = 0; i < n; i++) {
                System.out.printf("%x\n",buffer[i]);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

字节流转化成字符流

    public static void main(String[] args) {
        try(InputStream inputStream = new FileInputStream("e:/test.txt")){
            Scanner scanner = new Scanner(inputStream);
            //字节流转化成字符流
            String s = scanner.next();
            System.out.println(s);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
OutStream 输出

​ 每读写的最小单位,都是“字节”(8bit)

        try (OutputStream outputStream = new FileOutputStream("e:/test.txt",true)){
            String s = "你好世界";
            outputStream.write(s.getBytes());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

把字节流转化创了字符流

    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("e:/test.txt")){
            PrintWriter writer = new PrintWriter(outputStream);
            //把字节流转化创了字符流
            //PrintWriter 这样的类,在进行写入的时候,不一定直接写进硬盘,而是存进内存构成的“缓冲区中(buffer)”
            //为了确保数据被写进硬盘,需要用flush方法手动刷新缓冲区
            writer.println("hello");
            writer.flush();//刷新缓冲区

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
2.字符流:对应文本文件
Reader 输入

Reader是一个抽象类,不能直接new 。

read方法的三种版本:

在这里插入图片描述

1.无参数read,一次读一个字符。

        File file = new File("e:/test.txt");
        Reader reader = new FileReader("e:/test.txt");
        while (true){
            int c = reader.read();
            if (c==-1){
                break;
            }
            char ch = (char) c;
            System.out.println(ch);
        }

2.一次会读取若干个字符,会把参数指定的cbuf数组填充满,填不满也没关系,读的是所有的数据

  • 往read中传入一个空的数组,由read方法来对数组内容进行填充。
  • cbuf 称为”输出型参数“,类似于拿着餐盘打饭
        //一次read多个
        while (true){
            char[]cbuf = new char[1024];//数组大小
            int n = reader.read(cbuf); 
            //n表示当前读到的字符个数
            if (n==-1){
                break;
            }
            System.out.println("n= "+n);
            for (int i = 0; i < n; i++) {
                System.out.println(cbuf[i]);
            }
        }

3.一次会读取若干个字符,会把参数指定的cbuf数组,从off到len的范围填满

      reader.close();
  • 在读取完成之后,一定要进行close操作!!!

​ 防止文件资源泄露:使用close方法,最主要的目的,是为了释放文件描述符。文件描述符表是一个顺序表的结构,一个进程每打开一个文件,就需要在这个表里分配一个元素,但是整个顺序表的大小是有上限的。如果代码一直打开文件,而不去关闭文件,就会占满顺序表,后续打开文件就会出错。

        try {
            while (true){
                char[]cbuf = new char[1024];
                int n = reader.read(cbuf);
                //n表示当前读到的字符个数
                if (n==-1){
                    break;
                }
                System.out.println("n= "+n);
                for (int i = 0; i < n; i++) {
                    System.out.println(cbuf[i]);
                }
            }
        }finally {
            reader.close();
        }

无论try当中的代码是正常执行的,还是出现异常终止,都不会影响finally当中的close执行。

  • try with resources 语法:在()中定义的变量(实现closeable接口的对象),会在try结束的时候自动调用其close方法。
        try ( Reader reader = new FileReader("e:/test.txt")){
            while (true){
                char[]cbuf = new char[1024];
                int n = reader.read(cbuf);
                if (n==-1){
                    break;
                }
                System.out.println("n= "+n);
                for (int i = 0; i < n; i++) {
                    System.out.println(cbuf[i]);
                }
            }
        }

在这里插入图片描述

  • read方法的返回值是int类型的。如果真的读到了字符,会返回0~65535(无符号 char能表示的范围),如果到达了流的末尾,就会返回-1,表示文件读完了。

  • 同时Java标准库内部,对字符编码进行了很多处理。如果只使用char,字符集就固定是unicode。如果使用String,就会自动的把每个字符的unicode转换成utf-8。

Writer 输出
    public static void main(String[] args) {
        try(Writer writer = new FileWriter("e:/test.txt")) {
            writer.write("学习writer.write方法");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
  • Writer写入文件,默认会把原有的文件内容清空,再写入。如果不想清空,需要在构造方法中多加参数

    try(Writer writer = new FileWriter("e:/test.txt",true)) {
    

​ 每次读写的最小单位,都是“字符”(一个字符可能对应多个字节)一个中文字符:GBK(2字节)UTF-8 (3字节)

字符流本质上是对字节流的进一步封装。字符流可以自动的把文件中相邻的字节,转换成一个字符。(自动查字符集表)

输入和输出,是根据CPU的视角出发的。

demo
  • 扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
    public static void main(String[] args) {
        //先让用户输入一个要扫描的目录
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入要扫描的路径");
        String path = sc.next();
        File rootPath = new File(path);
        if (!rootPath.isDirectory()) {
            System.out.println("你输入的扫描路径有误");
            return;
        }

        //输入要查询的关键词
        System.out.println("请输入要删除文件的关键词");
        String word = sc.next();

        //进行递归扫描
        scanDir(rootPath, word);

    }

    private static void scanDir(File rootPath, String word) {
        //列出所有的文件和目录
        File[] files = rootPath.listFiles();
        if (files == null) {
            return;
        }
        for (File f : files) {
            System.out.println("当前扫描的路径"+f.getAbsolutePath());
            if (f.isFile()) {
                //文件
                checkDelete(f, word);//检查文件是否需要删除
            } else {
                //目录
                scanDir(f, word);//递归子目录
            }
        }
    }

    private static void checkDelete(File f, String word) {
        if (!f.getName().contains(word)){
            return;
        }
        System.out.println("当前文件为:"+f.getAbsolutePath()+"请确认是否删除(Y/N)");
        Scanner scanner = new Scanner(System.in);
        String choice = scanner.next();
        if (choice.equals("Y")||choice.equals("y")){
            f.delete();
            System.out.println("删除成功");
        }else {
            System.out.println("取消删除");
        }
    }

点击移步博客主页,欢迎光临~

偷cyk的图