文件IO

文件IO

一、文件

文件系统采用树形结构(树状目录结构) 组织目录和文件,核心是根节点为起点、目录作分支、文件为叶子的层级架构,再通过路径精准定位任意文件/目录,这是操作系统管理存储资源的基础方式,能让海量文件的分类、查找、管理更有序。

树形结构

树形结构模仿现实中的“树”,自上而下分层,核心元素只有目录(文件夹)文件,无环路、单根溯源,规则非常清晰:

  1. 根目录:整个树形结构的唯一起点。
    • Windows:每个磁盘分区有独立根目录(如C:\D:\),是分区的树形根;
    • Linux/macOS:全系统只有一个根目录/,所有磁盘、目录、文件都挂载在其下。
  2. 目录:用于分类存放子目录和文件,可多层嵌套,相当于树的“树枝”,本身不存储实际数据,只记录下级元素的名称、位置、属性等信息。
    • 例如:文档目录下可建工作生活子目录,工作下再建2026子目录,形成多层分支。
  3. 文件:树形结构的最终载体,用于存储实际数据(文本、图片、程序等),无下级元素,相当于树的“树叶”,必须依附于某一级目录存在,不能独立于树形结构。
  4. 层级关系:所有目录/文件都有唯一的上级节点(根目录无上级),子节点从属于父节点,形成“父目录-子目录/文件”的明确归属,避免文件混乱。

目录

目录是树形结构的“骨架”,本质是特殊的管理文件,核心作用不是存数据,而是索引和组织

  1. 分类管理:按用途、类型、归属将文件分组(如把办公文档放工作目录、视频放影音目录);
  2. 路径索引:记录下级所有元素的名称和位置,为“路径定位”提供基础;
  3. 权限隔离:可单独为目录设置访问权限(如只读、可读写),间接管理目录内文件的权限;
  4. 避免重名:同一目录下不允许有同名的文件/目录,但不同目录可重名(如C:\工作\笔记.txtD:\生活\笔记.txt可同时存在)。

文件路径

路径是从指定起点到目标文件/目录的层级导航字符串,本质是树形结构中“从起点到叶子/分支的唯一路径”,操作系统通过路径能精准找到任意文件/目录,分为绝对路径相对路径两种,是操作文件的核心标识。

绝对路径

从根目录(根节点)开始的完整路径,包含从根到目标的所有层级目录,唯一标识一个文件/目录,无论当前处于树形结构的哪个位置,绝对路径都能精准定位。

  • Windows格式:根目录(磁盘符+\)+ 层级目录 + 文件名,目录分隔符为**反斜杠**
    示例:C:\Users\张三\文档\工作\2026\项目计划.xlsx(从C盘根目录到目标文件的完整层级)
  • Linux/macOS格式:根目录/ + 层级目录 + 文件名,目录分隔符为正斜杠/
    示例:/home/zhangsan/Documents/work/2026/project.xlsx(从系统根目录到目标文件的完整层级)

相对路径

从当前所在目录(当前节点)开始的路径,仅包含“当前目录到目标”的层级,依赖当前位置,位置变则相对路径变,优势是简洁、灵活。

  • 核心符号:
    • . :代表当前目录(可省略);
    • .. :代表上级目录(父目录)。
  • 示例(以Windows为例,当前目录为C:\Users\张三\文档):
    1. 定位工作\2026\项目计划.xlsx:相对路径为工作\2026\项目计划.xlsx(或.\工作\2026\项目计划.xlsx);
    2. 定位C:\Users\张三\桌面\壁纸.jpg:相对路径为..\桌面\壁纸.jpg..回到上级张三目录,再进入桌面);
    3. 定位C:\软件\QQ.exe:相对路径为..\..\软件\QQ.exe(两次..回到C盘根目录,再进入软件)。

二、文件操作

在Java中,对文件系统的核心操作通过java.io.File类实现,该类封装了操作系统的文件系统API,让开发者无需直接与底层操作系统交互,即可完成文件/目录的创建、查询、删除等常用操作。

文件系统操作

核心属性

修饰符及类型属性说明
static StringpathSeparator依赖于系统的路径分隔符(字符串形式),Windows为;,Linux/macOS为:
static charpathSeparatorChar依赖于系统的路径分隔符(字符形式),Windows为;,Linux/macOS为:

构造方法

签名说明
File(File parent, String child)根据父目录(File对象)+ 子文件/目录路径,创建新的File实例
File(String pathname)根据文件/目录路径(绝对路径或相对路径),创建新的File实例
File(String parent, String child)根据父目录路径 + 子文件/目录路径,创建新的File实例

获取文件的路径信息

import java.io.File;
import java.io.IOException;

public class FilePathDemo {
    public static void main(String[] args) throws IOException {
        // 可创建绝对路径或相对路径的File对象
        // 注意:Java中可使用正斜杠/作为路径分隔符,兼容所有操作系统
        // 如果使用IDEA运行,相对路径基准是项目根目录
        // 如果使用命令行运行,则基准是当前执行命令的目录
        File file = new File("C:/myData/1.txt");
        
        // 打印各类路径相关信息
        System.out.println("父目录路径:" + file.getParent());
        System.out.println("转为File类型:" + file.getParentFile());
        System.out.println("文件/目录名称:" + file.getName());
        System.out.println("原始路径:" + file.getPath());
        System.out.println("绝对路径:" + file.getAbsolutePath());
        System.out.println("规范化绝对路径:" + file.getCanonicalPath());
    }
}

判断文件状态并创建空文件

import java.io.File;
import java.io.IOException;

public class FileExistAndCreateDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("C:/myData/1.txt");
        
        // 判断文件/目录的各类状态
        System.out.println("文件是否存在:" + file.exists());
        System.out.println("是否为普通文件:" + file.isFile());
        System.out.println("是否为目录:" + file.isDirectory());
        
        // 创建空白普通文件(若文件已存在则返回false,目录不存在则抛出IOException)
        boolean isCreated = file.createNewFile();
        System.out.println("空文件是否创建成功:" + isCreated);
    }
}

删除文件(目录)

import java.io.File;
import java.io.IOException;

public class FileDeleteDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("C:/myData/1.txt");
        
        // 直接删除文件/空目录(非空目录删除失败,返回false)
        boolean isDeleted = file.delete();
        System.out.println("文件是否直接删除成功:" + isDeleted);
        
        // 标记文件/目录在JVM退出时删除(适合清理临时文件,不立即执行)
        file.deleteOnExit();
    }
}

列出目录下的所有内容

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

public class FileListDemo {
    public static void main(String[] args) throws IOException {
        // 定位当前目录(. 代表当前目录)
        File file = new File("./");
        
        // 列出目录下所有文件/目录的名称数组
        String[] list = file.list();
        System.out.println("目录下所有内容名称:" + Arrays.toString(list));
        
        // 列出目录下所有文件/目录的File对象数组(更便于后续操作,推荐使用)
        File[] fileList = file.listFiles();
        System.out.println("目录下所有内容File对象:" + Arrays.toString(fileList));
    }
}

创建单级/多级目录

import java.io.File;

public class FileMkdirDemo {
    public static void main(String[] args) {
        File singleDir = new File("./111");
        File multiDir = new File("./111/222/333/444");
        
        // 创建单级目录(上级目录不存在则返回false)
        boolean isSingleDirCreated = singleDir.mkdir();
        System.out.println("单级目录是否创建成功:" + isSingleDirCreated);
        
        // 创建多级目录(上级目录不存在则自动创建,常用)
        boolean isMultiDirCreated = multiDir.mkdirs();
        System.out.println("多级目录是否创建成功:" + isMultiDirCreated);
        
        // 删除目录(仅能删除空目录,这里删除最内层的444目录)
        boolean isDirDeleted = multiDir.delete();
        System.out.println("空目录是否删除成功:" + isDirDeleted);
    }
}

修改文件名/移动文件(目录)

import java.io.File;

public class Demo {
    public static void main(String[] args) {
        File filesrc = new File("./oldFile.txt");
        File filedest = new File("./newFile.txt");
        filesrc.renameTo(filedest);
    }
}

判定权限

import java.io.File;

public class Demo {
    public static void main(String[] args) {
        File file = new File("./1.txt");
        System.out.println(file.canRead());
        System.out.println(file.canWrite());
        System.out.println(file.canExecute());
    }
}

文件内容操作

Java标准库中提供了一系列类来表示“流”。一种是字节流,一种是字符流。字节流针对文件读写的基本单位是“字节”,一般用于操作二进制文件;字符流是针对文件读写的基本单位是"字符",一般用于操作文本文件。

字节流

字节流的核心类有InputStream(FileInputStream)和OutputStream(FileOutputStream)。

读取文件内容

import java.io.*;

public class Demo {
    public static void main(String[] args) throws IOException {
        // InputStream 是Java所有字节输入流的顶级抽象类
        // 抽象类无法通过 new 关键字直接实例化,必须使用其具体的实现类
        // FileInputStream 继承自 InputStream ,专门用于从文件中读取字节数据
        // 它的构造方法支持两种入参:字符串格式的文件路径,File 类型的文件对象
        InputStream inputStream = new FileInputStream("./1.txt");
        
        // read() 无参方法,使用 while 循环打印
        while (true) {
            // 从输入流中读取单个字节的数据,并将其转换为 int 类型返回(范围 0-255)
            // 成功读取到字节时,返回该字节对应的十进制整数;当读取到文件末尾时,返回 -1
            int data = inputStream.read();
            if (data == -1) {
                break;
            }
            // 用十六进制打印读取结果
            System.out.printf("0x%X\n", data);
        }
        
        // read(byte[] b) 方法,效率更高,推荐使用
        while (true) {
            byte[] bytes = new byte[1024];
            // 传入字节数组,read()方法会尽量填满数组
            // 成功读取到字节时,返回一共读到的字节数;当读取到文件末尾时,返回 -1
            int n = inputStream.read(bytes);
            if (n == -1) {
                break;
            }
            for (int i = 0; i < n; i++) {
                System.out.printf("0x%X\n", bytes[i]);
            }
        }
        
        // read(byte[] b, int off, int len) 方法,只使用数组的一部分
        // off -> offset 当前位置到开头位置的距离
        // 适合读取结构化数据
        
        // 关闭文件操作
        inputStream.close();
    }
}

最后为了确保关闭文件操作必定运行,实际会套上 try-catch-finally,但这就会使得语法混乱。

import java.io.*;

public class Demo {
    public static void main(String[] args){

        InputStream inputStream = null;

        try {
            inputStream = new FileInputStream("./1.txt");
            while (true) {
                byte[] bytes = new byte[12];
                // 传入字节数组,read()方法会尽量填满数组
                // 成功读取到字节时,返回一共读到的字节数;当读取到文件末尾时,返回 -1
                int n = inputStream.read(bytes);
                if (n == -1) {
                    break;
                }
                for (int i = 0; i < n; i++) {
                    System.out.printf("0x%X\n", bytes[i]);
                }
            }
            // 语法混乱
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

因此,可以使用try-with-source写法,

import java.io.*;

public class Demo {
    public static void main(String[] args){
        // 此时 close() 就不必写了,编译器会生成隐式代码块,自动调用此方法
        // 可以在括号内通过分号定义多个对象(实现了closeable接口)
        try (InputStream inputStream = new FileInputStream("./1.txt")) {
            while (true) {
                byte[] bytes = new byte[12];
                int n = inputStream.read(bytes);
                if (n == -1) {
                    break;
                }
                for (int i = 0; i < n; i++) {
                    System.out.printf("0x%X\n", bytes[i]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

写入文件内容

import java.io.*;

public class Demo {
    public static void main(String[] args) {
        // 默认情况下,打开文件就会清空文件内容,采用追加写操作避免
        try (OutputStream outputStream = new FileOutputStream("./1.txt", true)) {
            // write()的三个方法与read()方法很类似
            outputStream.write(97); // 小写字母a
            outputStream.write(98);
            outputStream.write(99);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        
        // 一次写多个字节
        try (OutputStream outputStream = new FileOutputStream("./1.txt", true)) {
            byte[] bytes = {97, 98, 99};
            outputStream.write(bytes);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

字符流

字符类的核心类有Reader(FileReader)和Writer(FileWriter)。

读取文件内容

import java.io.*;

public class Demo {
    public static void main(String[] args) {
        try (Reader reader = new FileReader("./1.txt")) {
            while (true) {
                // 智能识别字符集并转换
                int data = reader.read();
                if (data == -1) {
                    break;
                }
                char c = (char) data;
                System.out.println(c);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        
        try (Reader reader = new FileReader("./1.txt")) {
            while (true) {
                char[] chars = new char[1024];
                int n = reader.read(chars);
                if (n == -1) {
                    break;
                }
                for (int i = 0; i < n; i++) {
                    char c = chars[i];
                    System.out.println(c);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

写入文件内容

import java.io.*;

public class Demo {
    public static void main(String[] args) {
        try (Writer writer = new FileWriter("./1.txt", true)) {
            // 一次写入一个字节
            writer.write('肉');

            // 一次多个字节
            char[] chars = {'你', '好', '肥'};
            writer.write(chars);

            // 一次写入多个字符
            String s = "你好肥";
            writer.write(s);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

三、文件IO实战

示例1

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

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

public class Demo {
    public static void main(String[] args) {
        // 用户输入
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要扫描的路径:");
        File baseFile = new File(scanner.next());
        System.out.println("请输入要搜索的字符串:");
        String word = scanner.next();
        // 校验内容
        if (!baseFile.exists() || !baseFile.isDirectory()) {
            System.out.println("输入的路径有误");
            return;
        }
        if (word.isEmpty()) {
            System.out.println("输入的字符串有误");
            return;
        }
        // 扫描路径
        scanDir(baseFile, word);
    }

    private static void deleteFile(File file) {
        Scanner scanner = new Scanner(System.in);
        String s = scanner.next();
        if (s.equals("Y") || s.equals("y")) {
            file.delete();
        }
    }

    private static void scanDir(File baseFile, String word) {
        File[] fileList = baseFile.listFiles();
        // 特殊情况
        if (fileList == null || fileList.length == 0) {
            return;
        }
        for (File file : fileList) {
            if (file.isDirectory()) { // 目录
                scanDir(file, word);
            } else if (file.isFile()) { // 文件
                System.out.println("正在扫描:" + file.getAbsolutePath());
                if (file.getPath().contains(word)) {
                    System.out.println("是否删除?Y/N");
                    deleteFile(file);
                }
            }
        }
    }
}

示例2

进行普通文件的复制。

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

public class Demo {
    public static void main(String[] args) {
        // 用户输入
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入要复制的源文件路径:");
        File srcFile = new File(scanner.next());
        System.out.println("输入要复制的目标文件路径:");
        File destFile = new File(scanner.next());
        // 校验内容
        if (!srcFile.exists() || !srcFile.isFile()) {
            System.out.println("输入的源文件不存在");
            return;
        }
        File destParentFile = new File(destFile.toString()).getParentFile();
        // 由于输入的是目标文件路径,是个文件
        // 因此需要判断它的上级目录是否存在
        if (!destParentFile.exists() || !destParentFile.isDirectory()) {
            System.out.println("输入的路径不存在");
            return;
        }
        // 拷贝操作(try-with-source操作,对输出流有自动创建功能)
        try (InputStream inputStream = new FileInputStream(srcFile);
             OutputStream outputStream = new FileOutputStream(destFile, true)) {
            while (true) {
                byte[] bytes = new byte[1024];
                int n = inputStream.read(bytes);
                if (n == -1) {
                    break;
                }
                outputStream.write(bytes, 0, n);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

示例3

扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不含目录),效率较低。

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

public class Demo {
    public static void main(String[] args) {
        // 用户输入
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要扫描的路径:");
        String basePath = scanner.next();
        System.out.println("请输入要搜索的关键词:");
        String word = scanner.next();
        // 校验内容
        File baseFile = new File(basePath);
        if (!baseFile.exists() || !baseFile.isDirectory()) {
            System.out.println("输入的路径不存在");
            return;
        }
        if (word.isEmpty()) {
            System.out.println("输入的搜索字符串不能为空");
            return;
        }
        // 递归搜索
        scanDir(baseFile, word);
    }

    private static void scanDir(File baseFile, String word) {
        File[] files = baseFile.listFiles();
        if (files == null || files.length == 0) {
            return;
        }
        for (File file : files) {
            System.out.println("当前搜索到文件: " + file.getAbsolutePath());
            if (file.isFile()) {
                dealFile(file, word);
            } else if (file.isDirectory()) {
                scanDir(file, word);
            }
        }
    }

    private static void dealFile(File file, String word) {
        // 先判定文件名是否包含
        if (file.getName().contains(word)) {
            System.out.println("找到文件名匹配的结果: " + file.getAbsolutePath());
            return;
        }
        // 判定文件内容是否包含
        StringBuilder content = new StringBuilder();
        try (Reader reader = new FileReader(file)) {
            while (true) {
                char[] chars = new char[1024];
                int n = reader.read(chars);
                if (n == -1) {
                    break;
                }
                content.append(chars, 0, n);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 判定文件内容是否包含 word
        if (content.indexOf(word) != -1) {
            System.out.println("找到文件内容匹配的结果: " + file.getAbsolutePath());
        }
    }
}
多线程(2) 2026-01-20
初识网络原理 2026-02-03

评论区