Java文件操作与流处理

文章目录

  • 一、文件
    • 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 文件的概念

在计算机中,文件是用于存储数据的基本单位。它可以包含文本、图像、音频、视频或其他类型的数据。文件可以用于保存程序代码、用户数据、操作系统配置等内容。每个文件都有一个唯一的名称,通过这个名称可以在计算机系统中找到和访问文件。

文件除了有数据内容之外,还有一部分信息,例如文件名、文件类型、文件大小等并不作为文件的数据而存在,我们把这部分信息可以视为文件的元信息。
Java文件操作与流处理_第1张图片

1.2 文件的组织结构

文件系统是计算机中用于组织和管理文件的一种结构。文件系统将文件组织成层次化的目录(或文件夹)结构,类似于树状结构。在大多数现代操作系统中,文件系统的顶层是根目录,下面可以有多个子目录,而每个子目录下又可以包含更多的文件或子目录。

文件系统的组织结构可以让用户更方便地管理和访问文件,通过目录结构可以更快速地定位到目标文件。
Java文件操作与流处理_第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 类提供了以下几个基本属性:

  1. 路径名(Path):文件或目录在文件系统中的路径。可以通过 getPath() 方法获取路径名。

  2. 绝对路径(Absolute Path):文件或目录在文件系统中的完整路径。可以通过 getAbsolutePath() 方法获取绝对路径。

  3. 父目录(Parent):文件或目录所在的上级目录。可以通过 getParent() 方法获取父目录的路径。

  4. 文件名(Name):文件或目录的名称,不包含路径信息。可以通过 getName() 方法获取文件名。

  5. 是否存在(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文件操作与流处理_第3张图片

三、字节流和字符流

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,则会覆盖原有文件的内容。

示例:使用OutputStreamHello, 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("取消删除!");
                    }
                }
            }
        }
    }
}

上述代码实现了一个简单的文件搜索和删除功能,以下是代码的功能描述和简要解释:

  1. 首先,程序会要求用户输入一个要搜索的目录路径。

  2. 接着,程序会验证用户输入的路径是否为一个目录,如果不是目录,则提示输入错误并结束程序。

  3. 然后,程序会要求用户输入一个要删除的文件名。

  4. 接下来,程序会递归地扫描指定路径下的所有文件和子目录,查找指定要删除的文件名。

  5. 如果找到了与要删除的文件名匹配的文件,程序会询问用户是否确认删除该文件。用户可以输入 “true” 确认删除,或输入 “false” 取消删除。

  6. 如果用户确认删除,程序将执行文件删除操作,并输出 “删除成功!”,否则输出 “取消删除!”。

  7. 程序使用递归的方式对目录进行扫描,以找到指定的文件并进行删除。

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

上述代码实现了一个简单的文件复制功能。以下是代码的功能描述和简要解释:

  1. 程序要求用户输入两个路径:源文件路径和目标文件路径。

  2. 然后,程序会验证用户输入的源文件路径是否为一个已存在的文件,如果不是文件,则提示输入错误并结束程序。

  3. 接着,程序会验证用户输入的目标文件路径是否为一个不存在的文件,如果是文件,则提示输入错误并结束程序。如果目标文件不存在,会创建一个新的目标文件。

  4. 然后,程序使用 FileInputStreamFileOutputStream 分别创建输入流和输出流,用于读取源文件内容和写入目标文件内容。

  5. 程序通过一个循环逐字节地读取源文件内容,并将读取到的字节写入目标文件。循环终止条件是读取到源文件的结尾(即 read() 方法返回 -1)。

  6. 当循环结束后,表示文件复制完成,程序输出 “复制完成”。

你可能感兴趣的:(Java进阶,java,开发语言,IO)