文章目录
- 一、文件
- 1.1 文件的概念
- 1.2 文件的组织结构
- 1.3 绝对路径和相对路径
 
- 二、文件操作File类
- 2.1 属性和常用方法
- 2.2 使用案例
 
- 三、字节流和字符流
- 3.1 InputStream 和 FileInputStream
- 3.2 使用 Scanner 读取字符
- 3.2 OutputStream 和 FileOutputStream
- 3.3 Reader 和 FileReader
- 3.4 Writer 和 FileWriter
 
- 四、文件操作案例
- 4.1 删除文件
- 4.2 复制文件
 
一、文件
1.1 文件的概念
在计算机中,文件是用于存储数据的基本单位。它可以包含文本、图像、音频、视频或其他类型的数据。文件可以用于保存程序代码、用户数据、操作系统配置等内容。每个文件都有一个唯一的名称,通过这个名称可以在计算机系统中找到和访问文件。
文件除了有数据内容之外,还有一部分信息,例如文件名、文件类型、文件大小等并不作为文件的数据而存在,我们把这部分信息可以视为文件的元信息。
 
1.2 文件的组织结构
文件系统是计算机中用于组织和管理文件的一种结构。文件系统将文件组织成层次化的目录(或文件夹)结构,类似于树状结构。在大多数现代操作系统中,文件系统的顶层是根目录,下面可以有多个子目录,而每个子目录下又可以包含更多的文件或子目录。
文件系统的组织结构可以让用户更方便地管理和访问文件,通过目录结构可以更快速地定位到目标文件。
 
1.3 绝对路径和相对路径
1. 绝对路径
 绝对路径是文件或目录在文件系统中的完整路径。它从根目录开始描述文件的路径,一直延伸到目标文件所在的目录。绝对路径的写法因操作系统而异。
- 例如在Windows系统中,一个文件的绝对路径可能是类似这样的:"C:\Users\Username\Documents\file.txt";
- 而在类Unix系统中,绝对路径可能是类似这样的:"/home/username/documents/file.txt"。
2. 相对路径
 相对路径是相当于当前工作目录的路径,当前工作目录是指用户当前所在的目录位置。相对路径不需要从根目录开始,它只需要描述从当前目录到目标文件的相对位置。相对路径的写法也因操作系统而异,但是相对路径在不同操作系统中通常更加简洁。例如,在当前目录下的一个文件,其相对路径可以是:"file.txt"。
 另外值得注意的是:
- ./表示当前目录;
- ../表示上级目录。
二、文件操作File类
File类是在 Java 中用于处理文件和目录的标准类。它提供了许多的属性和方法,用于管理文件以及目录的创建、文件的读取、写入等操作。
2.1 属性和常用方法
1. 属性
File 类提供了以下几个基本属性:
-  路径名(Path):文件或目录在文件系统中的路径。可以通过 getPath()方法获取路径名。
-  绝对路径(Absolute Path):文件或目录在文件系统中的完整路径。可以通过 getAbsolutePath()方法获取绝对路径。
-  父目录(Parent):文件或目录所在的上级目录。可以通过 getParent()方法获取父目录的路径。
-  文件名(Name):文件或目录的名称,不包含路径信息。可以通过 getName()方法获取文件名。
-  是否存在(Exists):判断文件或目录是否存在。可以通过 exists()方法来检查文件或目录是否存在。
这些属性可以帮助我们更好地了解文件或目录的位置和状态。例如,可以使用绝对路径来明确指定文件的位置,使用文件名来对文件进行标识,使用 exists() 方法来判断文件是否存在等等。
2. 构造方法
 File 类构造方法以及对应的描述如下:
| 构造方法 | 描述 | 
|---|---|
| File(String pathName) | 使用给定的路径名创建一个新的 File 实例。 | 
| File(String parent, String child) | 使用给定的父目录和子文件/目录名创建一个新的 File 实例。 | 
| File(File parent, String child) | 使用给定的父目录抽象路径和子文件/目录名创建一个新的 File 实例。 | 
3. 基本方法
 File 类的一些基本操作方法如下:
| 方法 | 描述 | 
|---|---|
| boolean createNewFile() | 在文件系统中创建新文件。 | 
| boolean mkdir() | 创建此抽象路径名指定的目录。 | 
| boolean mkdirs() | 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。 | 
| boolean delete() | 删除文件或目录。 | 
| boolean isFile() | 测试此抽象路径名表示的是否为文件。 | 
| boolean isDirectory() | 测试此抽象路径名表示的是否为目录。 | 
| String[] list() | 返回目录中的文件和子目录的名称数组。 | 
| File[] listFiles() | 返回目录中的文件和子目录的 File 对象数组。 | 
| long length() | 返回文件的长度(字节数)。 | 
| long lastModified() | 返回文件最后修改的时间戳。 | 
这些方法可以用于创建、删除、判断文件/目录类型、获取文件信息等基本的文件操作。
2.2 使用案例
1. 案例一:观察 get 系列方法的特点和差异
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());
}
运行结果:
null
test.txt
test.txt
D:\JavaCode\learn-code\code20230729\test.txt
D:\JavaCode\learn-code\code20230729\test.txt
2. 案例二:目录的创建
public static void main(String[] args) {
    File file = new File("test");
    System.out.println(file.exists());
    System.out.println(file.mkdirs());
    System.out.println(file.exists());
}
运行结果:
false
true
true
此时发现在当前工作目录下就多了一个test目录:
 
三、字节流和字符流
Java 中的 I/O 操作可以分为字节流和字符流。字节流主要用于处理二进制数据,而字符流用于处理文本数据。下面介绍四种常用的流类:
3.1 InputStream 和 FileInputStream
1. InputStream 类
 InputStream 是 Java 中用于读取字节数据的抽象类,它是所有字节输入流的基类。下面是一些常用的 InputStream 方法及其操作:
| 方法 | 描述 | 
|---|---|
| int read() | 从输入流中读取下一个字节的数据,返回读取的字节(0 到 255),如果已达到文件末尾,则返回 -1。 | 
| int read(byte[] b) | 从输入流中读取最多 b.length个字节的数据到字节数组b,返回实际读取的字节数,如果已达到文件末尾,则返回 -1。 | 
| int read(byte[] b, int off, int len) | 从输入流中读取最多 len个字节的数据到字节数组b的指定偏移量off处,返回实际读取的字节数,如果已达到文件末尾,则返回 -1。 | 
| long skip(long n) | 跳过输入流中的 n个字节的数据,返回实际跳过的字节数。 | 
| int available() | 返回可以从输入流中读取的字节数。 | 
| void close() | 关闭输入流并释放与其关联的所有资源。 | 
2. FileInputStream 类
InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,此次需要从文件中读取,所以使用就会FileInputStream类。
构造方法:
| 构造方法 | 描述 | 
|---|---|
| FileInputStream(File file) | 利用 File构造文件输入流 | 
| FileInputStream(String name) | 利用文件路径构造文件输入流 | 
读取文件案例:
首先在工作目录下创建一个test.txt的文本文件,其中的内容有hello world,然后使用read方法进行读取:
public static void main(String[] args) throws IOException {
    InputStream inputStream = new FileInputStream("test.txt");
    /*
    while (true){
        int b = inputStream.read();
        if(b==-1){
            break;
        }
        System.out.printf("%x \n", (int)b);
    }*/
    while (true){
        byte[] buffer = new byte[1024];
        int len = inputStream.read(buffer);
        System.out.println("len: " + len);
        if(-1 == len){
            break;
        }
        for (int i = 0; i < len; i++) {
            System.out.printf("%x ", buffer[i]);
        }
        System.out.println();
    }
    inputStream.close();
}
执行结果:
len: 11
68 65 6c 6c 6f 20 77 6f 72 6c 64 
len: -1
将读取到的结果,对照ASCII表就可以发现,其内容就是hello world。
3.2 使用 Scanner 读取字符
上述例子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我们使用一种我们之前比较熟悉的类来完成该工作,就是 Scanner 类。
 Scanner的构造方法有一个就是使用InputStream对象来构造的:
| 构造方法 | 说明 | 
|---|---|
| Scanner(InputStream is, String charset) | 使用 charset字符集进行is的扫描读取 | 
例如:使用InputStream对象来构造Scanner对象:
public static void main(String[] args) throws IOException {
    try (InputStream inputStream = new FileInputStream("test.txt");
         Scanner scanner = new Scanner(inputStream, "utf8")) {
        while (scanner.hasNext()) {
            String s = scanner.next();
            System.out.println(s);
        }
    }
}
3.2 OutputStream 和 FileOutputStream
1. OutputStream类
OutputStream 是 Java 中所有字节输出流的抽象基类。它用于向目标写入字节数据,是处理二进制数据的常用流类。OutputStream 提供了一系列方法来写入不同类型的数据到输出流中,并且可以通过其子类来实现具体的输出目标,如文件、网络连接等。
下面是 OutputStream 的几个基本方法以及对应的描述:
| 方法 | 描述 | 
|---|---|
| void write(int b) | 将指定的字节写入输出流。 | 
| void write(byte[] b) | 将字节数组中的所有字节写入输出流。 | 
| void write(byte[] b, int off, int len) | 将字节数组中的一部分字节写入输出流。 | 
| void flush() | 刷新输出流,将缓冲区的内容强制写入目标设备。 | 
| void close() | 关闭输出流并释放相关的资源。 | 
在使用 OutputStream 时,通常会使用其子类 FileOutputStream,并可能结合使用 BufferedOutputStream 进行缓冲输出,以提高写入性能。
2. FileOutputStream类
OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。在文件操作中,需要使用 FileOutputStream类,其构造方法如下:
| 构造方法 | 描述 | 
|---|---|
| FileOutputStream(File file) | 创建一个向指定 File 对象表示的文件中写入数据的输出流。 | 
| FileOutputStream(File file, boolean append) | 创建一个向指定 File 对象表示的文件中写入数据的输出流,并指定是否以追加方式写入数据。 | 
| FileOutputStream(String name) | 创建一个向指定文件名表示的文件中写入数据的输出流。 | 
| FileOutputStream(String name, boolean append) | 创建一个向指定文件名表示的文件中写入数据的输出流,并指定是否以追加方式写入数据。 | 
- 这些构造方法用于创建 FileOutputStream的实例,可以用于向指定的文件中写入数据。构造方法中的参数可以是一个File对象或一个表示文件名的字符串。
- 其中,如果指定的文件不存在,将会创建新的文件;
- 如果指定的文件已经存在,可以通过 append参数来控制是否以追加方式写入数据。
- 若 append参数为true,则数据会追加到文件的末尾;若为false,则会覆盖原有文件的内容。
示例:使用OutputStream将Hello, OutputStream!写入文件test.txt中:
import java.io.*;
public class OutputStreamExample {
    public static void main(String[] args) {
        String data = "Hello, OutputStream!";
        try (OutputStream outputStream = new FileOutputStream("test.txt")) {
            byte[] bytes = data.getBytes();
            outputStream.write(bytes);
            System.out.println("数据已经写入文件.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3.3 Reader 和 FileReader
1. Reader类
Reader 是 Java 中所有字符输入流的抽象基类,它用于从输入源(如文件、网络连接等)读取字符数据,适合处理文本数据。Reader 类提供了用于读取字符数据的方法,并可以通过其子类实现具体的输入源,如文件、字符串等。
基本方法如下:
| 方法签名 | 描述 | 
|---|---|
| int read() | 读取单个字符并返回字符的 Unicode 值。 | 
| int read(char[] cbuf) | 将字符读入数组 cbuf,并返回读取的字符数。 | 
| int read(char[] cbuf, int off, int len) | 将字符读入数组 cbuf 的指定位置,并返回读取的字符数。 | 
| int read(CharBuffer target) | 将字符读入 CharBuffer,并返回读取的字符数。 | 
| void close() | 关闭输入流并释放相关的资源。 | 
需要注意的是,在使用 Reader 读取字符数据时,要考虑字符编码的设置,以免出现乱码问题。同时,为了提高读取性能,通常会结合使用缓冲流(如 BufferedReader)对 Reader 进行包装,从而减少实际的读取操作。
Reader 的子类:
Java 中提供了多个 Reader 的子类,其中常用的包括:
- FileReader:用于从文件中读取字符数据。
- StringReader:用于从字符串中读取字符数据。
- BufferedReader:用于缓冲读取字符数据,提高读取性能。
2. FileReader类
构造方法:
| 构造方法 | 描述 | 
|---|---|
| FileReader(File file) | 创建一个从指定 File对象表示的文件中读取数据的FileReader。 | 
| FileReader(String fileName) | 创建一个从指定文件名表示的文件中读取数据的 FileReader。 | 
示例代码:使用 FileReader 读取文件:
import java.io.*;
public class ReaderExample {
    public static void main(String[] args) {
        try (Reader reader = new FileReader("test.txt")) {
            char[] buffer = new char[1024];
            int length;
            while ((length = reader.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, length));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3.4 Writer 和 FileWriter
1. Writer类
 Writer 是 Java 中所有字符输出流的抽象基类,它用于向目标写入字符数据,适合处理文本数据。Writer 类提供了用于写入字符数据的方法,并可以通过其子类实现具体的输出目标,如文件、字符串等。
基本方法如下:
| 方法签名 | 描述 | 
|---|---|
| void write(int c) | 写入单个字符。 | 
| void write(char[] cbuf) | 将字符数组中的所有字符写入输出流。 | 
| void write(char[] cbuf, int off, int len) | 将字符数组中的一部分字符写入输出流。 | 
| void write(String str) | 写入字符串。 | 
| void write(String str, int off, int len) | 写入字符串的一部分。 | 
| void flush() | 刷新输出流,将缓冲区的内容强制写入目标设备。 | 
| void close() | 关闭输出流并释放相关的资源。 | 
Writer 的子类:
Java 中提供了多个 Writer 的子类,其中常用的包括:
- FileWriter:用于向文件中写入字符数据。
- StringWriter:用于向字符串中写入字符数据。
- BufferedWriter:用于缓冲写入字符数据,提高写入性能。
2. FileWriter类
 构造方法:
| 构造方法 | 描述 | 
|---|---|
| FileWriter(File file) | 创建一个向指定 File 对象表示的文件中写入数据的字符输出流。 | 
| FileWriter(File file, boolean append) | 创建一个向指定 File 对象表示的文件中写入数据的字符输出流,并指定是否以追加方式写入数据。 | 
| FileWriter(String fileName) | 创建一个向指定文件名表示的文件中写入数据的字符输出流。 | 
| FileWriter(String fileName, boolean append) | 创建一个向指定文件名表示的文件中写入数据的字符输出流,并指定是否以追加方式写入数据。 | 
- 这些构造方法用于创建 FileWriter的实例,可以用于向指定的文件中写入字符数据。构造方法中的参数可以是一个 File 对象或一个表示文件名的字符串。
- 其中,如果指定的文件不存在,将会创建新的文件;
- 如果指定的文件已经存在,可以通过 append参数来控制是否以追加方式写入数据。
- 若 append参数为true,则数据会追加到文件的末尾;若为false,则会覆盖原有文件的内容。
示例代码:使用 FileWriter 写入文件:
import java.io.*;
public class WriterExample {
    public static void main(String[] args) {
        try (Writer writer = new FileWriter("test.txt")) {
            String data = "Hello, Writer!";
            writer.write(data);
            System.out.println("数据写入文件成功.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
四、文件操作案例
4.1 删除文件
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件。
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class IODemo {
    private static final Scanner scanner = new Scanner(System.in);
    public static void main(String[] args) throws IOException {
        // 让用户指定一个搜索的目录
        System.out.println("请输入要搜索的路径:");
        String basePath = scanner.next();
        // 针对用户输入的目录进行简单的判断
        File root = new File(basePath);
        if (!root.isDirectory()) {
            // 输入的不是目录
            System.out.println("输入的目录有误!");
            return;
        }
        // 再让用户输入一个要删除的文件名
        System.out.println("请输入要删除的文件名:");
        String nameToDelete = scanner.next();
        // 针对指定的路径进行扫描,递归
        // 先从根目录出发(root)
        // 先判断一下在当前这个目录中是否有要删除的文件,如果有则删除,没有就找下一个
        // 如果当前包含一些目录,就针对子目录进行递归查找
        scanDir(root, nameToDelete);
    }
    private static void scanDir(File root, String nameToDelete) throws IOException {
        System.out.println("[scanDir]:" + root.getCanonicalPath());
        // 列出当前目录下的文件
        File[] files = root.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                // 如果是目录就继续递归
                scanDir(file, nameToDelete);
            } else {
                // 如果是普通文件, 判断是否是要删除的文件
                if (file.getName().equals(nameToDelete)) {
                    System.out.println("确认是否要删除文件(true/false):" + file.getCanonicalPath());
                    boolean flag = Boolean.parseBoolean(scanner.next());
                    if (flag) {
                        boolean delete = file.delete();
                        System.out.println("删除成功!");
                    } else {
                        System.out.println("取消删除!");
                    }
                }
            }
        }
    }
}
上述代码实现了一个简单的文件搜索和删除功能,以下是代码的功能描述和简要解释:
-  首先,程序会要求用户输入一个要搜索的目录路径。 
-  接着,程序会验证用户输入的路径是否为一个目录,如果不是目录,则提示输入错误并结束程序。 
-  然后,程序会要求用户输入一个要删除的文件名。 
-  接下来,程序会递归地扫描指定路径下的所有文件和子目录,查找指定要删除的文件名。 
-  如果找到了与要删除的文件名匹配的文件,程序会询问用户是否确认删除该文件。用户可以输入 “true” 确认删除,或输入 “false” 取消删除。 
-  如果用户确认删除,程序将执行文件删除操作,并输出 “删除成功!”,否则输出 “取消删除!”。 
-  程序使用递归的方式对目录进行扫描,以找到指定的文件并进行删除。 
4.2 复制文件
import java.io.*;
import java.util.Scanner;
// 文件的复制
public class IODemo {
    public static void main(String[] args) throws IOException {
        // 输入两个路径:源和目标
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要拷贝的文件:");
        String srcPath = scanner.next();
        System.out.println("请输入要拷贝到的目的地:");
        String destPath = scanner.next();
        File srcFile = new File(srcPath);
        if(!srcFile.isFile()){
            System.out.println("当前输入的源路径有误!");
            return;
        }
        File destFile = new File(destPath);
        if(destFile.isFile()){
            System.out.println("当前输入的目标路径有误!");
            return;
        }
        destFile.createNewFile();
        // 进行拷贝操作
        try(InputStream inputStream = new FileInputStream(srcFile);
            OutputStream outputStream = new FileOutputStream(destFile)){
            while (true){
                int read = inputStream.read();
                if(read == -1){
                    System.out.println("复制完成");
                    break;
                }
                outputStream.write(read);
            }
        }
    }
}
上述代码实现了一个简单的文件复制功能。以下是代码的功能描述和简要解释:
-  程序要求用户输入两个路径:源文件路径和目标文件路径。 
-  然后,程序会验证用户输入的源文件路径是否为一个已存在的文件,如果不是文件,则提示输入错误并结束程序。 
-  接着,程序会验证用户输入的目标文件路径是否为一个不存在的文件,如果是文件,则提示输入错误并结束程序。如果目标文件不存在,会创建一个新的目标文件。 
-  然后,程序使用 FileInputStream和FileOutputStream分别创建输入流和输出流,用于读取源文件内容和写入目标文件内容。
-  程序通过一个循环逐字节地读取源文件内容,并将读取到的字节写入目标文件。循环终止条件是读取到源文件的结尾(即 read()方法返回 -1)。
-  当循环结束后,表示文件复制完成,程序输出 “复制完成”。 





![[STL]详解list模拟实现](https://img-blog.csdnimg.cn/img_convert/d0485487dcf0b76b3eaf15eaa7220ebb.png)












