文件操作 和 IO

目录

​编辑一、认识文件

1、文件路径

2、其它知识

二、Java 中操作文件

三、文件内容的读写

1、Reader

2、InputStream

3、输出


一、认识文件

 文件是在硬盘上存储数据的一种方式,操作系统帮我们把硬盘的一些细节都封装起来了

我们只需要了解文件相关的一些接口即可

硬盘是用来存储数据,和内存相比,硬盘的存储空间更大,访问速度更慢,成本更低,可以持久化存储

操作系统通过 “文件系统” 这样的模块来管理硬盘

实际上我的电脑只有一个硬盘,操作系统可以通过文件系统把这个硬盘抽象成多个硬盘一样 NTFS 是windows 上的文件系统,背后有一定的格式来组织硬盘数据

 EXT4 是 Linux 上常见的文件系统


1、文件路径

不同的文件系统,管理文件的方式都是类似的

通过 目录 - 文件 构成了 “ N 叉树” 树形结构

通过 D盘 - tmp - cat.jpg 这个路线,就能 找到 / 确定 电脑上的唯一一个文件,这个东西就称为 “路径”

在 windows 上,使用 / 或者 \ 来分割不同的目录

以盘符开头的路径,也叫做 “绝对路径”

绝对路径相当于与是从 “此电脑” 这里出发,找文件的过程

以 . 或者 ... 开头的目镜,叫做 “绝对路径”

相对路径,需要有一个 “基准目录” / “工作目录”,表示从这个基准出发,怎么走能找到这个文件夹

如果以 D:为基准目录 ./tmp/cat.jpg  如果以 D:tmp 为基准 ./cat.jpg (. 表示当前所在目录)

如果以 D:/tmp/111 为基准, ..cat.jpg(..表示当前目录的上一级目录)

同样是一个 cat.jpg 文件,站在不同的基准目录上找到的路径是不一样的


2、其它知识

文件系统上存储的文件,又可以分成两个大类

1、文本文件

存储的是字符

例如:utf-8 就是一个大表,这个表上的数据的组合,就可以称为字符

2、二进制文件

存储的是二进制的数据

如何判断文件是文本文件还是二进制文件?

一个最简单的方式:直接使用记事本打开

记事本打开文件,就是尝试把当前的数据,在码表中查询

如果打开之后能看懂,就是文本文件,如果打开之后看不懂,就是二进制文件


二、Java 中操作文件

后续针对文件的操作,文本和二进制的操作方式是不同的

文件系统操作:创建文件,删除文件,创建目录....

java 中,不要通过 java.io.file 类来对一个文件(包括目录)进行具体的描述

IO : input 和 output,我们是站在 cpu 的视角来看待输入输出的

通过 File 对象来描述到一个具体的文件

File 对象可以对应到一个真实存在的文件,也可以对应到一个不存在的文件

构造方法:

第二个构造方法此处的字符串,就表示一个路径,可以是绝对路径,也可以是相对路径

方法: 

站在操作系统的角度来看待:目录也是文件

操作系统的文件是一个更广义的概念,具体来说有很多不同的类型

1、普通文件(通常见到的文件)

2、目录文件(通常见到的文件夹)

windows 上,目录之间的分隔符,可以使用 / 也可以使用 \ 

Linux 和 mac 上面,就只支持 /

所以即使在 windows 上,也尽量使用 / ,使用 、 在代码中需要搭配转义字符

可以通过代码,来感受一下对文件的操作: 当我们把绝对路径改成相对路径,代码运行结果又会有所不同:

 getAbsolutePath 会将工作目录拼接上当前目录,就是运行的结果

在 idea 中运行一个程序,工作目录就是项目所在的目录,在命令行中 运行一个程序,工作目录就是命令行当前所在的目录,如果程序是运行在 tomacat 中,工作目录就是 tomcat 下的 bin 目录

这个操作是可能会抛出异常的

比如,当前写入的路径是一个非法的路径

比如,当前创建的这个文件,对于所在的目录没有权限操作

有的时候,可能会用到这样的一个功能:临时文件

程序运行的时候,搞一个临时文件,程序结束了,临时文件中自动删掉

像 office 等很多这样的生产力软件 都有产生临时文件功能,这个临时文件就自动保存了你当前的编辑的中间状态

有的人使用 word 不喜欢保存,用了一段时间之后,电脑突然断电关机了,没保存的数据难道就没了吗?

重启电脑,由于刚才是非正常关闭,临时文件是来不及删除,仍然存在的moffice 启动就能知道上次是异常关闭了,就会提示你是否要从之前的临时文件恢复未保存的数据 

 创建目录代码:

import java.io.File;
import java.io.FileReader;

public class Demo4 {
    public static void main(String[] args) {
        File file = new File("./test-dir");
        //mk -> make dir->directory
        //mkdir 一次只能创建一层目录,mkdirs 可以一次创建多层目录
        file.mkdir();
        //file.mkdirs();
    }
}

 文件重命名也可以起到文件移动的效果

import java.io.File;

//文件重命名
public class Demo5 {
    public static void main(String[] args) {
        File file = new File("./test.txt");
        File file2 = new File("./src/test2.txt");
        file.renameTo(file2);
    }
}

以上文件系统的操作,都是基于 File 类来完成的

另外还需要文件内容的操作


三、文件内容的读写

1、Reader

文件这里的内容本质是来自于硬盘,硬盘又是操作系统管理的,使用某个编程语言操作文件,本质上都是需要调用系统的 api 

虽然不同的编程语言操作文件的 api 有所差别,但是基本步骤都是一样的

文件内容操作的核心步骤有四个:

1、打开文件

2、关闭文件

3、读文件

4、写文件

Java IO流 是一个比较庞大的体系,涉及到非常多的类,这些不同的类,都有各自不同的特性,但是总的来说,使用的方法都是类似的

(1)构造方法,打开文件

(2)close 方法,关闭文件

(3)如果衍生自 InputStream 或者 Read ,就可以使用 read 方法来读数据

(4)如果衍生自 OutputStream 或者 Writer ,就可以使用 write 方法来写数据

 读文件操作:

 这个操作非常重要,释放必要的资源

让一个进程打开一个文件,是要从系统这里申请一定的资源的(占用进程的 pcb 的文件描述符表中的一个表项)

如果不释放,就会出现 “文件资源泄露” ,是一个很严重的问题

文件描述符表 可以理解成一个顺序表,长度有限,不会自动扩容,一旦一直打开文件,而不去关闭不用的文件,文件描述符表就会被占满,后续就没法打开新的文件了

我们可以使用 try with rewsourses 来避免出现上述问题

此时只要 try 代码执行完毕了,就会自动调用到 close 方法

文件流中的任意对象,都可以按照上述讨论来进行 close

一次读一个 char 类型的数组

 会把读到的内容,填充到参数的这个 cbuf 数组中,此处的参数,相当于是一个输出形参数

通过 read ,就会把本来是空的一个数组,填充上内容

n 表示实际读到的字符的个数

相当于给了 1024 这么大空间,如果文件足够大,超过 1024,就能填满这个空间

如果文件比较小,不足1024,就会把文件所有内容都填到数组中(剩下会空余)

返回值 n 就表示实际读到的字符的个数

有时候,可能会涉及到有多个小文件,都需要读取并且拼接到一起,就可以使用这个方法

假设现在有三个文件,每个文件的大小是 100 字节

 最终代码如下:

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

//Reader 的使用
public class Demo6 {
    public static void main(String[] args) throws IOException {
/*        //FileReder 的构造方法,可以填写一个路径(绝对路径和相对路径都可以),也可以写一个构造好了的 file 对象
        Reader reader = new FileReader("d:/test.txt");
        try {
            //中间的代码无论出现什么情况,close 都可以执行到
        }finally {
            //如果抛出异常或者 return ,close就都执行不到了
            reader.close();
        }*/

        //上述使用 finally 的方式能解决问题,但是不优雅
        //使用 try with resourses 是更好的解决方法
        try(Reader reader = new FileReader("d:/test.txt")){
            while(true){
                char buf[] = new char[1024];
                int n = reader.read(buf);
                if(n == -1){
                    //读到文件末尾了
                    break;
                }
                for(int i = 0;i < n;i++){
                    System.out.print(buf[i] + " ");
                }
            }
        }
    }
}

2、InputStream

InputStream 是字节流,用法和 Reader 相似

文本文件,也可以使用字节流打开,只不过此时你读到的每个字节,就不是完整的字符了

 一次读一个字节

 一次读若干个字节,尝试填满这个 byte[]

一次读若干个字节,填满数组的一部分

Java 虽然有 char ,但是很少会用,更多的是使用 String 

此处,我们可以借助一些额外的工具类,就可以完成 字节 / 字符 --> 字符串 的转化

虽然也可以直接使用 String 的构造方法完成 char[] 或者 byte[] 到字符串的转化,但是比较麻烦

这个工具类就是 Scanner !!!

操作系统中,所谓的文件,是一个广义的概念,System.in 是一个特殊的文件,对应到 “标准输入”,普通的硬盘上的文件,也是文件

后来网络编程中的网卡(socket),也是文件

Scanner 都是一视同仁的,只是把当前读到的字节数据进行转换,但并不关心这个数据是来自于哪里

但是,要注意,Scanner 只是用来读取文本文件的,不是适合读取二进制文件

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class Demo8 {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("d:/test.txt")){
            Scanner scanner = new Scanner(inputStream);
            //此时就是从 test.txt 这个文件中读取数据了
            String s = scanner.next();
            System.out.println(s);
        }

    }
}

3、输出

输出的使用方法和输入非常相似,关键操作是 write 

write 之前要打开文件,write 之后也需要关闭文件

输出流对象(无论是字节流还是字符流),会在打开文件之后,清空文件内容!!!

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class Demo8 {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("d:/test.txt")){
            Scanner scanner = new Scanner(inputStream);
            //此时就是从 test.txt 这个文件中读取数据了
            String s = scanner.next();
            System.out.println(s);
        }

    }
}

还可以按照追加写的方式打开,此时就不会清空内容了 

读操作和写操作,也都能支持随机访问,可以移动光标到指定位置进行读写,此处就不进行介绍了

OutputStream 方式使用方法完全一样

只不过 write 方法,不能支持 字符串 参数,只能按照 字节 或者 字节数组来写入


实例:扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除此文件

import java.io.File;
import java.util.Scanner;

public class Demo10 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //1、用户输入一个目录,后续的查找都是针对这个目录进行的
        System.out.println("请输入要搜索的目录");
        File rootPath = new File(scanner.next());

        //2、再让用户输入要搜索和删除的关键粗
        System.out.println("请输入要删除的关键字");
        String word = scanner.next();

        //3、判断当前目录是否有效
        if (!rootPath.isDirectory()){
            System.out.println("此时输入的路径不是合法目录");
            return;
        }

        //4、遍历目录,从根目录出发,按照深度优先(递归) 的方式进行
        scanDir(rootPath,word);
    }
    public static void scanDir(File currentDir,String word){
        //1、先列出当前目录中包含哪些内容
        File[] files = currentDir.listFiles();
        if (files == null || files.length == 0){
            //空的目录或者非法的目录
            return;
        }
        //2、遍历列出的文件,分两个情况进行讨论
        for(File f : files){
            if (f.isFile()){
                //如果当前文件是普通文件,看看文件名是否包含了 word ,来决定是否要删除
                dealFile(f,word);
            }else {
                //如果当前文件是目录文件,就递归执行 scanDir
                scanDir(f,word);
            }
        }
    }
    public static void dealFile(File f,String word){
        Scanner scanner = new Scanner(System.in);
        //1、先判断当前文件是否包含 word
        if (!f.getName().contains(word)){
            //此时文件不包含 word,
            return;
        }
        //2、包含 word,询问用户是否删除该文件
        System.out.println("该文件是: " + f.getAbsolutePath() + ",是否确认删除(Y / N )");
        String choice = scanner.next();
        if (choice.equals("Y") || choice.equals("y")){
            f.delete();
        }
    }
}

示例:进行普通的文件复制

import java.io.*;
import java.util.Scanner;

public class Demo11 {
    public static void main(String[] args) throws IOException {
        System.out.println("请输入要复制的文件路径");
        Scanner scanner = new Scanner(System.in);
        String src = scanner.next();
        File srcfile= new File(src);
        if (!srcfile.isFile()){
            System.out.println("您输入的源文件路径是非法的");
            return;
        }
        System.out.println("请输入要复制到的目标路径");
        String dest = scanner.next();
        File destfile= new File(dest);
        //不要求目标文件存在,但是得保证目标文件所在的目录是存在的
        if (!destfile.getParentFile().isDirectory()){
            System.out.println("您输入的目标文件路径是非法的");
            return;
        }

        //2、进行复制操作的过程,按照字节流打开
        try(InputStream inputStream = new FileInputStream(srcfile);
            OutputStream outputStream = new FileOutputStream(destfile)){
                while(true){
                    byte[] buffer = new byte[1024];
                    int n = inputStream.read(buffer);
                    if (n == -1){
                        System.out.println("读取到 eof,循环结束");
                        break;
                    }
                    outputStream.write(buffer,0,n);
                }
            }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_73616913/article/details/132257024
今日推荐