文件操作 和 IO

文件操作 和 IO_第1张图片

目录

​编辑一、认识文件

1、文件路径

2、其它知识

二、Java 中操作文件

三、文件内容的读写

1、Reader

2、InputStream

3、输出


一、认识文件

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

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

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

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

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

 EXT4 是 Linux 上常见的文件系统


1、文件路径

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

通过 目录 - 文件 构成了 “ N 叉树” 树形结构文件操作 和 IO_第3张图片

通过 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 的视角来看待输入输出的

文件操作 和 IO_第4张图片通过 File 对象来描述到一个具体的文件

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

构造方法:

文件操作 和 IO_第5张图片

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

方法: 

文件操作 和 IO_第6张图片

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

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

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

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

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

Linux 和 mac 上面,就只支持 /

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

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

 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、写文件

文件操作 和 IO_第9张图片

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 字节文件操作 和 IO_第10张图片

 最终代码如下:

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);
                }
            }
    }
}

你可能感兴趣的:(Java,EE,单片机,嵌入式硬件,java,linux,服务器)