一、文件
文件系统采用树形结构(树状目录结构) 组织目录和文件,核心是根节点为起点、目录作分支、文件为叶子的层级架构,再通过路径精准定位任意文件/目录,这是操作系统管理存储资源的基础方式,能让海量文件的分类、查找、管理更有序。
树形结构
树形结构模仿现实中的“树”,自上而下分层,核心元素只有目录(文件夹) 和文件,无环路、单根溯源,规则非常清晰:
- 根目录:整个树形结构的唯一起点。
- Windows:每个磁盘分区有独立根目录(如
C:\、D:\),是分区的树形根; - Linux/macOS:全系统只有一个根目录
/,所有磁盘、目录、文件都挂载在其下。
- Windows:每个磁盘分区有独立根目录(如
- 目录:用于分类存放子目录和文件,可多层嵌套,相当于树的“树枝”,本身不存储实际数据,只记录下级元素的名称、位置、属性等信息。
- 例如:
文档目录下可建工作和生活子目录,工作下再建2026子目录,形成多层分支。
- 例如:
- 文件:树形结构的最终载体,用于存储实际数据(文本、图片、程序等),无下级元素,相当于树的“树叶”,必须依附于某一级目录存在,不能独立于树形结构。
- 层级关系:所有目录/文件都有唯一的上级节点(根目录无上级),子节点从属于父节点,形成“父目录-子目录/文件”的明确归属,避免文件混乱。
目录
目录是树形结构的“骨架”,本质是特殊的管理文件,核心作用不是存数据,而是索引和组织:
- 分类管理:按用途、类型、归属将文件分组(如把办公文档放
工作目录、视频放影音目录); - 路径索引:记录下级所有元素的名称和位置,为“路径定位”提供基础;
- 权限隔离:可单独为目录设置访问权限(如只读、可读写),间接管理目录内文件的权限;
- 避免重名:同一目录下不允许有同名的文件/目录,但不同目录可重名(如
C:\工作\笔记.txt和D:\生活\笔记.txt可同时存在)。
文件路径
路径是从指定起点到目标文件/目录的层级导航字符串,本质是树形结构中“从起点到叶子/分支的唯一路径”,操作系统通过路径能精准找到任意文件/目录,分为绝对路径和相对路径两种,是操作文件的核心标识。
绝对路径
从根目录(根节点)开始的完整路径,包含从根到目标的所有层级目录,唯一标识一个文件/目录,无论当前处于树形结构的哪个位置,绝对路径都能精准定位。
- Windows格式:根目录(磁盘符+
\)+ 层级目录 + 文件名,目录分隔符为**反斜杠**
示例:C:\Users\张三\文档\工作\2026\项目计划.xlsx(从C盘根目录到目标文件的完整层级) - Linux/macOS格式:根目录
/+ 层级目录 + 文件名,目录分隔符为正斜杠/
示例:/home/zhangsan/Documents/work/2026/project.xlsx(从系统根目录到目标文件的完整层级)
相对路径
从当前所在目录(当前节点)开始的路径,仅包含“当前目录到目标”的层级,依赖当前位置,位置变则相对路径变,优势是简洁、灵活。
- 核心符号:
.:代表当前目录(可省略);..:代表上级目录(父目录)。
- 示例(以Windows为例,当前目录为
C:\Users\张三\文档):- 定位
工作\2026\项目计划.xlsx:相对路径为工作\2026\项目计划.xlsx(或.\工作\2026\项目计划.xlsx); - 定位
C:\Users\张三\桌面\壁纸.jpg:相对路径为..\桌面\壁纸.jpg(..回到上级张三目录,再进入桌面); - 定位
C:\软件\QQ.exe:相对路径为..\..\软件\QQ.exe(两次..回到C盘根目录,再进入软件)。
- 定位
二、文件操作
在Java中,对文件系统的核心操作通过java.io.File类实现,该类封装了操作系统的文件系统API,让开发者无需直接与底层操作系统交互,即可完成文件/目录的创建、查询、删除等常用操作。
文件系统操作
核心属性
| 修饰符及类型 | 属性 | 说明 |
|---|---|---|
static String | pathSeparator | 依赖于系统的路径分隔符(字符串形式),Windows为;,Linux/macOS为: |
static char | pathSeparatorChar | 依赖于系统的路径分隔符(字符形式),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());
}
}
}