文件IO操作

文章目录

  • 文件IO操作
    • 一、认识文件
      • 1. 文件路径
      • 2. 其他知识
    • 二、java 中操作文件
      • 1. File 概述
        • 1) 构造方法
        • 2) 属性
        • 3) 方法
      • 2. 示例代码
        • 示例1
        • 示例2
        • 实例3
    • 三、文件内容的读写
      • 1. 字符流输入
        • Reader 使用
      • 2. 字节流输入
        • InputStream 使用
          • -利用 Scanner 读取文件
      • 3. 输出
    • 四、实例演示

文件IO操作

一、认识文件

文件是在硬盘上存储数据的方式。操作系统帮我们把硬盘的一些细节都封装起来了。程序猿只需要了解文件的相关接口即可。

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

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实际上我们的电脑上只有一个硬盘,操作系统可以通过文件系统把这个硬盘抽象成多个硬盘一样。


1. 文件路径

不同的文件系统,管理文件的方式都是类似的。通过目录——文件构成了“N叉树”树形结构。

文件IO操作_第1张图片

比如说现在要找 cat 这个图片

此电脑 => D盘 => tmp => cat.jpg ( d:/tmp/cat.jpg )

通过这个路线就能找到/确定电脑上唯一一个文件,这个东西就叫做路径。

windows 上用 / 或者 \ 来分割不同的目录。

比如:d:/tmp/cat.jpg 或者 d:\tmp\cat.jpg

以上述这个目录为例,以盘符开头的路径,可以叫做“绝对路径”,以 . 或者 … 开头的路径,叫做“相对路径

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

文件IO操作_第2张图片

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

2. 其他知识

文件系统上存储的文件,具体来说又分成两个大类。

  1. 文本文件

    存储的是字符

  2. 二进制文件

    存储的是二进制数据

一个最简单的方式来,判断文件是二进制还是文本,就是直接用记事本打开,能看懂的是文本文件,看不懂的则是二进制文件。

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

当我们站在CPU角度上来看待 输入输出 时,如下图:

文件IO操作_第3张图片


二、java 中操作文件

Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述。

1. File 概述

1) 构造方法

下面参数的字符串,就表示一个路径。可以是绝对路径,也可以是相对路径。

签名 说明
File(File parent, Stringchild) 根据父目录 + 孩子文件路径,创建一个新的 File 实例
File(String pathname) 根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径
File(String parent, Stringchild) 根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示
2) 属性

windows 上目录之间的分隔符两种都支持,可以使用 / 也可以使用 \ 。而 linux / mac 就只是支持 /

即使在 windows,也尽量使用 / 。如果要使用 \ 则需要在代码中搭配转义字符。

修饰符及类型 属性 说明
static String pathSeparator 依赖于系统的路径分隔符,String 类型的表示
static char pathSeparator 依赖于系统的路径分隔符,char 类型的表示
3) 方法
修饰符及返回值类型 方法签名 说明
String getParent() 返回 File 对象的父目录文件路径
String getName() 返回 FIle 对象的纯文件名称
String getPath() 返回 File 对象的文件路径
String getAbsolutePath() 返回 File 对象的绝对路径
String getCanonicalPath() 返回 File 对象的修饰过的绝对路径
boolean exists() 判断 File 对象描述的文件是否真实存在
boolean isDirectory() 判断 File 对象代表的文件是否是一个目录
boolean isFile() 判断 File 对象代表的文件是否是一个普通文件
boolean createNewFile() 根据 File 对象,自动创建一个空文件。成功创建后返回 true
boolean delete() 根据 File 对象,删除该文件。成功删除后返回 true
void deleteOnExit() 根据 File 对象,标注文件将被删除,删除动作会到JVM运行结束时才会进行
String[] list() 返回 File 对象代表的目录下的所有文件名
File[] listFiles() 返回 File 对象代表的目录下的所有文件,以 File 对象表示
boolean mkdir() 创建 File 对象代表的目录
boolean mkdirs() 创建 File 对象代表的目录,如果必要,会创建父目录和中间目录
boolean renameTo(Filedest) 进行文件改名,也可以视为我们平时的剪切、粘贴操作
boolean canRead() 判断用户是否对文件有可读权限
boolean canWrite() 判断用户是否对文件有可写权限

2. 示例代码

在 idea 中运行一个程序,工作目录就是项目所在的目录。

示例1
import java.io.File;
import java.io.IOException;

public class practice1 {
    public static void main(String[] args) throws IOException {
        File file = new File("../aaa/test.txt");

        System.out.println("get.getPath(): " + file.getPath());
        System.out.println("get.getParent(): " + file.getParent());
        System.out.println("get.getName(): " + file.getName());
        System.out.println("get.getAbsolutePath(): " + file.getAbsolutePath());
        //把工作目录 拼接上 当前目录
        System.out.println("get.getCanonicalPath(): " + file.getCanonicalPath());
    }
}

输出结果为:

文件IO操作_第4张图片

示例2
import java.io.File;

public class practice2 {
    public static void main(String[] args) {
        File file = new File("d:/temp/aaa/2222");   	//是一个目录
        //File file = new File("d:/temp/aaa/2222/test.txt"); 	//是一个文件
        
        System.out.println(file.isDirectory());
        System.out.println(file.isFile());
        System.out.println(file.exists());
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实例3

mkdir()只能创建一层目录,如果父目录不存在,则创建失败。例如,如果你想在e:/test目录下创建一个名为example的子目录,但是test目录不存在,则创建失败。

而mkdirs()可以创建多层目录,如果父目录不存在,则会一并创建。例如,如果你想在e:/test目录下创建一个名为example的子目录,但是test目录不存在,则mkdirs()方法会先创建test目录,然后再创建example目录。

import java.io.File;

public class practice5 {
    public static void main(String[] args) {
        File file = new File("./temp11");
        File file1 = new File("./src/temp111");

        file.renameTo(file1);   //可以进行类似于粘贴、剪切的操作
    }
}

文件IO操作_第5张图片


三、文件内容的读写

以上文件系统的操作,都是基于File类来完成的。另外还需要文件内容的操作。我们可以称之为文件流 stream

当我们想读取100字节文件数据是时,可以一次读完,也可以分两次一次读 50,十次一次读10字节等等…

关于文件内容的读写有一系列类:

  • 字节流 (操作字节为单位,二进制文本)
    1. InputStream
    2. OutputStream
  • 字符流 (操作字符为单位,文本文件)
    1. Reader
    2. Writer

后续一些操作字节类/字符的类都是由上述这两个类所衍生出来的。

如果使用 InputStream 或 Reader ,就可以使用 read 方法来读数据

如果使用 OutputStream 或 Writer,就可以使用 write 方法来写数据

对于文件操作来说,打开文件、操作文件、关闭文件都是十分重要的环节。

当我们申请打开一个文件后,是需要从系统这里申请一定的资源的。(占用进程的 pcb 里的文件描述符表中的一个选项,其中这个文件描述符表可以看做为一个顺序表,其长度有限,不会进行自动扩容)

用完文件后如果没有及时关闭释放,就会出现“文件资源泄露”很严重的问题。一旦一直打开文件,而不去关闭不用的文件,文件的描述符表就会被吃满,后续就无法继续打开新的文件了。

这里关闭文件,介绍一种简单的方式,后续文件操作都可以使用——try with resources.

用于自动关闭实现了各种接口的资源,在try语句块中声明资源,当try语句块执行完毕后,会自动关闭这些资源,无需手动调用 close() 方法。try-with-resources语句也可以包含catch和finally语句,用于处理异常和执行必要的清理操作。这种语法的使用可以简化代码,提高代码的可读性和可维护性。


1. 字符流输入

Reader 使用
import java.io.*;

public class practice6 {
    public static void main(String[] args) throws IOException {
        try(Reader reader = new FileReader("d:/test.txt")) {    //使用try with resources
            while (true) {
                char[] buf = new char[1024];
                int n = reader.read(buf);
                if (n == -1) {
                    break;
                }
                for (int i = 0; i < 1 ; i++) {
                    System.out.print(buf[i]);
                }
            }
        }
    }
}

read方法:

文件IO操作_第6张图片

read(char[] cbuf) :会把读到的内容,填充到 cbuf 数组中,一次读一个字符直到把数组填满。此处的参数就相当于“输出型参数”。通过 read 可以把一个空数组填充上内容。(就相当于食堂打饭,要先拿个餐盘去装东西)

read 返回值:int n = reader.read(buf)这里的返回值 n 代表实际读到的字符个数,如果读完就会返回 -1。(0~65535 正是两个字节的范围)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

问:utf8 格式下,一个字符应该是 3 个字节,这里读出来的一个字符是 2 个字节??

java 的 char 类型是使用 unicode 编码的(一个字符两个字节),使用这个方法读一个字符,java 标准库内部会帮我们自动完成转换。程序员们感知不到。

有时候会涉及到很多个小文件,需要读取并合并到一起去,就会用到上述办法read(char[] cbuf)

文件IO操作_第7张图片

往往读一个比较大的文件,需要循环读取


2. 字节流输入

FileInputStream 构造方法:

签名 说明
FileInputStream(File file) 利用 File 构造文件输入流
FileInputStream(String name) 利用文件路径构造文件输入流
InputStream 使用

方法:

修饰符及返回类型 方法签名 说明
int read () 读取一个字节的数据,返回 -1 代表已经完全读完了
int read (byte[] b) 最多读取 b.length 字节的数据到 b 中,返回实际读到的数量;-1 代表以及读完了
int read (byte[] b, int off, int len) 最多读取 len - off 字节的数据到 b 中,放在从 off 开始,返回实际读到的数量;-1 代表以及读完了
void close () 关闭字节流

注:InputSream只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使用 FileInputStream

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

InputStream 用法和 Reader 十分相似。

read方法:

文件IO操作_第8张图片

read(byte[] b):同样是把读到的内容填到数组中,一次读一个字节直到把数组填满

import java.io.*;

public class practice6 {
    public static void main(String[] args) throws IOException {
        try(InputStream reader = new FileInputStream("d:/test.txt")) {   //文件内容:你好世界
            while (true) {
                byte[] buf = new byte[1024];
                int n = reader.read(buf);
                if (n == -1) {
                    break;
                }
                System.out.println("n = " + n);           //输出:n = 12
                for (int i = 0; i < n; i++) {             
                    System.out.printf("%x ",buf[i]);      //输出:e4 bd a0 e5 a5 bd e4 b8 96 e7 95 8c
                }
                String s = new String(buf, 0, n, "utf-8"); //通过String构造方法,把字节转化成字符。
                System.out.print(s);                      //输出:你好世界
            }
        }
    }
}

我们查看 UTF-8 编码:

文件IO操作_第9张图片

可以看出来,通过字节流,read会把文本中的每个字节都读出来。此时我们确实把字节都读出来了,但是还需要借助工具,把字节转换成字符——直接使用 String 的构造方法。

但是可以看到,利用 String 构造方法比较麻烦,也不优雅。这里介绍一个新方法——Scanner。

-利用 Scanner 读取文件

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

Scanner都是一视同仁的。只是把当前读到的字节数据进行转换,不关心这个数据究竟是来自于标准输入,还是来自于文件。

以前学过的Scanner的操作,在这里完全适用。

public class practice6 {
    public static void main(String[] args) throws IOException {
        try(InputStream reader = new FileInputStream("d:/test.txt")) {
            Scanner scanner = new Scanner(reader);
            String s1 = scanner.next();
            System.out.println(s1);           //输出:你好世界
        }
    }
}

3. 输出

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

write 之前要打开文件,用完了也需要关闭文件。

输出有一个特别需要注意的点,输出流对象(无论是字符流还是字节流)会在打开文件后,清空文件内容。

但是我们可以按照追加写的方式来打开文件,此时就不会清空内容了。

//后面的 true 就代表可以追加数据 append-true
try(Writer writer = new FileWriter("d:/test.txt",true)){}

OutputStream 使用方法,完全一样。

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

Scanner 搭配 InputStream 实现简化代码的效果

PrintWriter 则是搭配 OutputStream


四、实例演示

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

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

public class practice8 {
    public static Scanner scanner = new Scanner(System.in);
    public static void main(String[] args) throws IOException {
        System.out.println("请输入要查询的根目录:");
        File rootPath = new File(scanner.next());
        if(!rootPath.isDirectory()) {
            System.out.println("输入的根目录格式不正确!!");
            return;
        }
        System.out.println("请输入要删除的文件名:");
        String name = scanner.next();

        scanDir(rootPath, name);
    }

    public static void scanDir(File rootPath, String name) {
        File[] files = rootPath.listFiles();
        if(files == null || files.length == 0){
            return;
        }

        for (File file : files) {
            System.out.println(file.getAbsolutePath());

            if(file.isFile()) {
                deleteFile(file, name);
            } else {
                scanDir(file, name);
            }
        }
    }

    public  static void deleteFile(File f, String name) {
        if(!f.getName().contains(name)) {
            return;
        }

        System.out.println("该文件是:" + f.getName() + ",是否确认要删除?(Y/N)");
        String choice = scanner.next();
        if(choice.equals("Y") || choice.equals("y")) {
            System.out.println("删除成功!");
            f.delete();
        }
    }
}

2.进行普通文件的复制

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

public class practice9 {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入源文件地址:");
        File src = new File(scanner.next());
        if (!src.isFile()) {
            System.out.println("您输入的源文件地址不合法!");
            return;
        }
        System.out.println("请输入目标文件地址:");
        File dest = new File(scanner.next());
        if (!dest.isFile()) {
            System.out.println("您输入的目标文件地址不合法!");
            return;
        }

        try (InputStream inputStream = new FileInputStream(src);
             OutputStream outputStream = new FileOutputStream(dest)) {
            while (true) {
                byte[] buf = new byte[1024];
                int n = inputStream.read(buf);
                System.out.println("读到的字节个数为:" + n);
                if (n == -1) {
                    System.out.println("读取到eof,循环结束!!");
                    break;
                }
                outputStream.write(buf, 0, n);
            }
        }
    }
}

你可能感兴趣的:(JavaEE,文件IO操作)