最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Java文件操作如何入门?一文搞懂File类和IO流
时间:2026-07-24 09:15:55 编辑:袖梨 来源:一聚教程网
一、认识文件系统
1.1 什么是文件
文件是对硬盘等持久化存储设备中数据单位的抽象。我们日常办公桌上的一份份纸质文件,在计算机中就以文件的形式存在。

每个文件不仅仅是数据的容器,还包含了一些元信息,比如文件名、文件类型、文件大小、创建时间、修改时间等,这部分信息称为文件的元信息。
1.2 树形结构组织
随着文件数量的增加,如何有效管理文件成了一个重要问题。计算机采用了树形结构来组织文件。这种结构让我们可以创建文件夹(也就是目录)来分类存放文件。目录也是文件。
在电脑中的文件系统,根目录就像书房的总体空间,子目录就像各个分区,最终的文件就像每一本具体的书。
1.3 文件路径(Path)
在树形文件系统中,使用路径(Path)可以准确定位一个唯一的文件。
通常使用斜杠/ 分割路径中的多级目录;在主流操作系统都是使用/分割,Windows默认使用反斜杠,也支持斜杠/。
在代码中涉及到路径最好使用斜杠/,因为单个反斜杠加字符表示转移字符,写两个反斜杠才表示一个反斜杠。
绝对路径(Absolute Path) 从根目录开始,完整描述文件位置的路径。比如在Windows系统中,
C:Users张三Documentsreport.docx就是一个绝对路径,它从盘符开始,逐级定位到目标文件。相对路径(Relative Path) 从当前工作目录出发,描述文件位置的路径。例如,如果当前在
C:Users张三目录下,那么Documentsreport.docx就是一个相对路径。在路径中,.代表当前目录,..代表父目录。../..表示上两层目录- 在IDEA中直接运行,基准路径就是项目的目录
1.4 文件的分类
从数据存储的角度,文件主要分为两大类:
文本文件:存储的是被字符集编码后的文本数据。比如我们平时用记事本编写的
.txt文件,用UTF-8编码保存的Java源文件等。当我们用文本编辑器打开这类文件时,能够看到人类可读的文字。二进制文件:按照特定格式存储的非文本数据。比如图片(.jpg、.png)、音频(.mp3)、视频(.mp4)以及可执行程序(.exe)等。这类文件需要用专门的软件才能正确解析和显示。
在Windows操作系统中,系统通常通过文件的扩展名(如.txt、.docx、.exe)来识别文件类型并决定用哪个程序打开。而在Linux、Unix等系统中,文件类型并不严格依赖于扩展名,而是通过文件的内部特征或权限位来判断。
1.5 文件权限与特殊文件
操作系统为了数据安全,为文件设置了权限系统,通常包括:
- 可读(r):允许读取文件内容
- 可写(w):允许修改文件内容
- 可执行(x):允许将文件作为程序运行
在Linux系统中,每个文件都有针对文件所有者、同组用户和其他用户的独立权限设置。
此外,还有一种特殊文件叫做快捷方式(Windows)或软链接(Linux),它们并不存储实际数据,而是指向另一个真实文件的引用,类似"指针"。
Unix/Linux操作系统秉承"万物皆文件"的设计哲学,将所有的I/O设备(如键盘、鼠标、显示器、网络接口)都抽象为文件,这使得设备操作可以统一使用文件读写接口。
二、Java中的File类
java.io.File是Java抽象文件/目录的核心类,仅负责获取元数据、创建删除文件、遍历目录、处理路径,无法读取/写入文件内部数据。
也就是说new File(路径)仅创建内存对象,磁盘上对应的文件不一定真实存在。
2.1 File构造方法
| 构造方法 | 功能说明 |
|---|---|
| File(String pathname) | 传入绝对/相对路径创建对象,最常用 |
| File(File parent, String child) | 父目录File对象 + 子文件/目录名 |
| File(String parent, String child) | 父目录字符串 + 子名称,拼接路径 |
2.2 File核心API
2.2.1 路径信息获取
| 方法 | 返回值 | 作用 |
|---|---|---|
| getName() | String | 获取纯文件名/目录名,不含父路径 |
| getParent() | String | 获取父目录路径字符串 |
| getPath() | String | 获取构造File时传入的原始路径(相对/绝对原样返回) |
| getAbsolutePath() | String | 获取完整绝对路径,不简化..、.符号 |
| getCanonicalPath() | String | 规范绝对路径,自动解析..、.,推荐打印日志使用 |
File file = new File("./helloworld.txt");
System.out.println(file.getName());// helloworld.txt
System.out.println(file.getPath());// .helloworld.txt
System.out.println(file.getParent());// .
System.out.println(file.getAbsolutePath());
// D:Codecode-IDEAEETestJ20260707IO.helloworld.txt
System.out.println(file.getCanonicalPath());
// D:Codecode-IDEAEETestJ20260707IOhelloworld.txt
..会被getCanonicalPath()解析为上一级目录,输出干净无冗余符号的完整路径。
2.2.2 文件状态判断
| 方法 | 返回值 | 功能 |
|---|---|---|
| exists() | boolean | 判断磁盘文件/目录是否真实存在 |
| isFile() | boolean | 判断是否为普通文件(非目录) |
| isDirectory() | boolean | 判断是否为文件夹 |
| canRead()/canWrite() | boolean | 判断读写权限 |
2.2.3 文件与目录创建删除
| 方法 | 返回值 | 功能 |
|---|---|---|
| createNewFile() | boolean | 仅创建空普通文件;父目录不存在、文件已存在、无权限、空间不足均创建失败返回false,文件不存在则创建成功返回true |
| mkdir() | boolean | 仅创建路径最后一级目录,中间父目录不存在时直接创建失败 |
| mkdirs() | boolean | 递归自动创建所有缺失的中间父目录,开发中优先使用 |
| delete() | boolean | 立即删除文件或空目录;非空目录无法直接删除,需先递归清空内部所有文件 |
| deleteOnExit() | void | 标记文件,程序正常退出时自动删除,多用于临时缓存文件 |
2.2.4 目录遍历与重命名
| 方法 | 返回值 | 功能 |
|---|---|---|
| list() | String[] | 返回目录下所有子文件名称字符串数组;若路径不是目录,返回null |
| listFiles() | File[] | 返回目录下所有子文件对应的File对象数组,支持递归深度遍历 |
| renameTo(File dest) | boolean | 传入目标File对象;同层级路径为重命名,不同层级为移动文件;跨磁盘分区仅能复制后删除,无法直接移动 |
三、字节流 InputStream / OutputStream
File类只能操作文件属性,读写文件内容依靠IO流。
IO流分为两大体系:
- 字节流:处理所有文件
- 字符流:仅处理文本文件。
3.1 IO流基础概念
流是数据传输的管道,类比水管:
- 输入流(InputStream):从文件读取数据到程序,相当于接水;
- 输出流(OutputStream):程序写入数据到文件,相当于放水;
字节流以 字节(byte,0~255) 为最小操作单位,支持图片、视频、程序、文本所有文件类型,通用性最强。
3.2 字节输入流 InputStream
InputStream顶层抽象父类,无法实例化,文件读取使用子类FileInputStream。
FileInputStream构造方法
| 签名 | 说明 |
|---|---|
| FileInputStream(File file) | 利用 File 对象构造文件输入流 |
| FileInputStream(String name) | 利用文件路径字符串构造文件输入流 |
FileInputStreamAPI
| 方法签名 | 功能说明 | 特殊返回/注意事项 |
|---|---|---|
| int read() | 读取单个字节 | 返回字节数值;文件读取完毕返回 -1(结束标记) |
| int read(byte[] buf) | 批量读取字节存入数组 | 返回本次实际读取的字节长度;无剩余数据返回 -1 |
| int read(byte[] buf, int off, int len) | 从数组下标 off 位置开始,最多读取 len 个字节存入缓冲区 | 读取完成同样返回实际读取长度,无数据返回 -1 |
| void close() | 关闭IO流,释放操作系统文件句柄资源 | 必须调用,否则会产生资源泄漏问题 |
打开与关闭文件
创建一个FileInputStream对象相当于打开文件,因此这个文件必须存在,否则会抛出FileNotFoundException异常。与打开文件对应的是关闭文件。打开文件类似于申请内存,关闭文件相当于释放内存。
对于JVM来说只需要申请内存,释放内存可以交给GC处理。而文件资源与内存资源不同,JVM可以自动管理内存,但是不可以管理文件。如果不手动关闭就会涉及到文件资源泄露。
程序每打开一个文件,就会在文件描述符表中申请一个表项,如果只申请不关闭,表项很快就会消耗完,后续再打开文件就会失败。
为了防止复杂逻辑导致关闭文件没有执行到,可以使用try-finally语句:
public static void main(String[] args) throws IOException {
InputStream in = null;
try{
in= new FileInputStream("./test.txt");
}finally {
in.close();
}
}
这段代码功能达到了,但是代码美观性不够。更推荐使用try-with-resources语法,只要实现AutoCloseable接口代码块结束自动调用close,无论是否异常。
public static void main(String[] args) throws IOException {
try(InputStream in = new FileInputStream("./test.txt")){
//读取文件
}
}
ReentrantLock不能用类似的方式,因为类必须实现AutoCloseable接口
两种读取方式
- 单字节read():循环一次读取1字节.
public static void main(String[] args) throws IOException {
try(InputStream in = new FileInputStream("./test.txt")){
//一次读一个字节
while(true){
int data = in.read();
if(data==-1)
break;
System.out.printf("%xn",data);
}
}
这种方式IO交互次数极多,读取大文件性能极差,仅适用于极小文件;
- 字节数组缓冲读取:一次性读取指定的字节数
public static void main(String[] args) throws IOException {
try(InputStream in = new FileInputStream("./test.txt")){
//一次读多个字节
while(true) {
//读一次尽可能把字节数组填满,n表示实际读到的字节数
byte[] data = new byte[1024];
int n = in.read(data);
if(n==-1)
break;
for (int i = 0; i < n; i++) {
System.out.printf("%x ", data[i]);
}
}
}
}
这中写法用参数作为函数的返回值,称为“输出型函数”。可以减少磁盘IO次数,是生产环境标准写法。
提出输出型参数的概念归根结底是语法的限制,函数只能返回一个值,而read()希望返回长度和读入的字节数组;像go,python这样的语言支持返回多个值,就不需要考虑输出型函数的问题。
3.3 字节输出流 OutputStream
文件写入使用子类FileOutputStream
| 方法签名 | 功能说明 | 特殊返回/注意事项 |
|---|---|---|
| void write(int b) | 写入单个字节 | 传入int数值,仅低8位有效;超出字节范围会自动截断 |
| void write(byte[] b) | 写入完整字节数组 | 将数组内全部字节一次性写出,缓冲区未满时数据暂存内存 |
| void write(byte[] b, int off, int len) | 从数组下标off位置开始,最多写出len个字节到流 | 需保证off、len不越界数组,否则抛出数组下标越界异常 |
| void flush() | 刷新缓冲区 | 强制将缓冲区暂存数据写入目标;带缓冲输出流必须调用,否则数据丢失 |
| void close() | 关闭IO流,释放操作系统文件句柄资源 | 关闭前会自动执行一次flush;必须调用,否则产生资源泄漏 |
覆盖写与追加写
对于OutputStream默认创建不存在的文件,但是会清空文件内容,重新写文件,这是操作系统的特性,其他语言也有类似的效果。
FileOutputStream构造方法第二个布尔参数控制写入模式:
new FileOutputStream("a.txt"):默认覆盖,清空原有内容再写入;new FileOutputStream("a.txt", true):追加模式,在文件末尾新增内容。
缓冲区与flush()
磁盘IO速度远低于内存,OutputStream内置内存缓冲区:写入数据先暂存内存,缓冲区填满才批量写入磁盘,减少IO次数提升性能。
隐患:程序未写满缓冲区直接结束,内存残留数据丢失。解决方案:写入完成调用flush()强制将缓冲区数据刷入磁盘;close()关闭前会自动执行flush。
四、字符流与转换流
字节流读写中文需要手动编码转换,操作繁琐;字符流以字符char为单位,原生适配文本,底层依赖字节流+字符集转换。
4.1 转换流:字节流与字符流桥梁
InputStreamReader、OutputStreamWriter是转换流核心,手动指定UTF-8编码,避免系统默认编码不一致导致乱码。
InputStreamReader:FileInputStream→ 字符输入流;OutputStreamWriter:FileOutputStream→ 字符输出流。
4.2 PrintWriter:便捷文本输出工具
PrintWriter封装转换流,提供print()、println()、printf()等和控制台输出一致的方法,开发文本日志、配置文件首选。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class PrintWriterTest {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("log.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
PrintWriter writer = new PrintWriter(osw)) {
writer.println("第一行日志数据");
writer.printf("数字:%d,字符串:%s%n", 100, "IO测试");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.3 Scanner读取文本文件
读取文本简易方案,封装字符解码,直接按分隔符读取字符串,适合简单文本解析:
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Scanner;
public class ScannerReadTest {
public static void main(String[] args) throws IOException {
try (FileInputStream fis = new FileInputStream("hello.txt");
Scanner scanner = new Scanner(fis, "UTF-8")) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}
}
}
}
五、案例演示
1. 递归扫描目录,检索指定名称文件并支持删除
输入扫描目录、检索关键词,递归遍历所有子文件夹,匹配文件名的文件列出,询问用户是否删除。
import java.io.File;
import java.util.Scanner;
public class demo06 {
static Scanner scan = new Scanner(System.in);
public static void main(String[] args) {
System.out.println("输入要搜索的目录:");
String rootDir = scan.next();
File rootFile = new File(rootDir);
if(!rootFile.isDirectory()){
System.out.println("输入的路径不是目录!");
return;
}else if(!rootFile.exists()){
System.out.println("文件不存在!");
return;
}
System.out.println("请输入待检索的关键字:");
String key = scan.next();
scanFiles(rootFile,key);
}
private static void scanFiles(File rootFile, String key) {
// 得到目录里的所有文件
File[] files = rootFile.listFiles();
if(files==null)
return;
for(File file : files){
if(file.isDirectory()){
scanFiles(file,key);
}else{
if(file.getName().contains(key)){
System.out.println("检索到含 "+key+" 的文件:"+file.getAbsolutePath()+",是否删除 Y/N");
String input = scan.next();
if("Y".equals(input)){
file.delete();
System.out.println("文件已删除");
}
}
}
}
}
}
2. 通用文件复制工具
输入源文件、目标路径,判断文件是否存在,使用字节缓冲区复制。
import java.io.*;
import java.util.Scanner;
public class demo07 {
static Scanner scan = new Scanner(System.in);
public static void main(String[] args) throws IOException {
System.out.println("请输入源文件路径:");
String srcPath = scan.nextLine();
File srcFile = new File(srcPath);
if(!srcFile.isFile()){
System.out.println("源文件不存在或不是文件!");
return;
}
System.out.println("请输入目的文件路径:");
String destPath = scan.nextLine();
File destFile = new File(destPath);
if(!destFile.getParentFile().isDirectory()){
System.out.println("目的地址的路径不存在!");
return;
}
try(InputStream in = new FileInputStream(srcFile);
OutputStream out = new FileOutputStream(destFile)){
int i = 0;
while(true){
byte[] data = new byte[1024];
int n = in.read(data);
if(n==-1)
break;
//data不一定填满,要按照实际读入的长度写
out.write(data,0,n);
System.out.println(i);
i++;
}
System.out.println("复制完成!");
}
}
}
3. 检索目录中文件名/文件内容包含关键词的文本文件
需求:递归遍历目录,不仅匹配文件名,还读取文本内容,内容包含关键词也筛选出来,适合日志检索。
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class SearchContentTool {
private static List<File> result = new ArrayList<>();
public static void main(String[] args) throws IOException {
Scanner input = new Scanner(System.in);
System.out.print("扫描目录:");
String root = input.next();
File rootDir = new File(root);
if (!rootDir.isDirectory()) return;
System.out.print("检索关键词:");
String key = input.next();
scanAll(rootDir, key);
System.out.println("匹配文件总数:" + result.size());
for (File f : result) {
System.out.println(f.getCanonicalPath());
}
}
private static void scanAll(File dir, String key) throws IOException {
File[] files = dir.listFiles();
if (files == null) return;
for (File f : files) {
if (f.isDirectory()) {
scanAll(f, key);
} else {
// 文件名匹配 或 文件内容匹配
if (f.getName().contains(key) || contentMatch(f, key)) {
result.add(f);
}
}
}
}
// 判断文件内容是否包含关键词
private static boolean contentMatch(File file, String keyword) throws IOException {
StringBuilder sb = new StringBuilder();
try (InputStream is = new FileInputStream(file);
Scanner sc = new Scanner(is, "UTF-8")) {
while (sc.hasNextLine()) {
sb.append(sc.nextLine());
}
}
return sb.toString().contains(keyword);
}
}
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持本站。
您可能感兴趣的文章:- Java文件操作与IO流详解(File类+字节流+字符流)
- 一起来了解Java的File类和IO流
- Java文件操作之IO流 File类的使用详解
- Java中IO流 RandomAccessFile类实例详解
- Java IO流 File类的常用API实例