从0开始学习Java+AI知识点总结-11.File、字符集、IO流
本文从数据存储方案入手,详细讲解了 File 类的文件操作、递归算法的核心思想、字符集的编码解码规则,以及 IO 流的完整体系(字节流、字符流、缓冲流等),最后介绍了 Commons-IO 框架的简化用法。System.out.println("复制完成,耗时:" + (end - start) + " 毫秒");
在 Java 开发中,文件操作和数据读写是核心技能之一,无论是数据持久化、配置文件处理还是日志记录,都离不开对文件和 IO 流的掌握。
一、数据存储:从临时到持久
在程序运行过程中,数据的存储方式决定了其生命周期。了解不同存储方案的特点,能帮助我们选择合适的方式保存数据。
1. 内存中的临时存储
我们日常使用的变量、对象、数组、集合等都是内存中的数据容器,例如:
double money = 9999.5; // 变量 Student s = new Student(); // 对象 int[] age = new int[100]; // 数组 List<Student> students = new ArrayList<>(); // 集合 |
特点:这些数据仅在程序运行期间存在,一旦程序结束或发生断电,数据会立即丢失,无法长期保存。
2. 文件:持久化存储方案
文件是存储在磁盘上的持久化数据载体,具有以下优势:
- 持久性:即使程序终止或断电,数据依然保存在磁盘中,不会丢失。
- 通用性:可存储文本、图片、视频、音频等多种类型的数据。
在 Java 中,我们通过 File 类操作文件本身(如创建、删除、获取信息),通过 IO 流读写文件内容,实现数据的持久化管理。
二、File 类:操作文件与文件夹的工具
java.io.File 类是 Java 操作文件系统的基础,它用于代表磁盘中的文件或文件夹,但只能对文件 / 文件夹本身进行操作,不能读写文件内容。
1. 创建 File 对象
File 类提供了三种常用构造器,用于根据路径创建文件对象:
构造器 |
说明 |
public File(String pathname) |
根据文件路径(绝对路径或相对路径)创建对象 |
public File(String parent, String child) |
根据父路径字符串和子路径字符串创建对象 |
public File(File parent, String child) |
根据父路径对象和子路径字符串创建对象 |
示例:
// 绝对路径:从盘符开始的完整路径 File file1 = new File("D:\\test\\a.txt"); // 相对路径:从当前工程目录开始的路径 File file2 = new File("src\\demo\\b.txt"); // 父路径+子路径 File parent = new File("D:\\test"); File file3 = new File(parent, "c.txt"); |
注意:File 对象仅封装路径信息,路径可以是真实存在的,也可以是不存在的(后续可通过方法创建)。
2. 路径的两种类型
- 绝对路径:从盘符开始的完整路径,例如 D:\\java\\file.txt,在任何环境下都能准确定位文件。
- 相对路径:不包含盘符,默认从当前工程的根目录开始查找,例如 src\\test.txt,更灵活,适合项目内文件操作。
3. File 类的核心方法
File 类提供了丰富的方法用于操作文件和文件夹,可分为以下几类:
(1)判断与获取信息
方法 |
说明 |
exists() |
判断路径对应的文件 / 文件夹是否存在,返回 boolean |
isFile() |
判断是否为文件,返回 boolean |
isDirectory() |
判断是否为文件夹,返回 boolean |
getName() |
获取文件 / 文件夹的名称(包含后缀) |
length() |
获取文件大小(单位:字节),文件夹无意义 |
lastModified() |
获取最后修改时间(毫秒值,可转换为日期) |
getAbsolutePath() |
获取绝对路径字符串 |
示例:
File file = new File("a.txt"); if (file.exists()) { System.out.println("是否为文件:" + file.isFile()); System.out.println("文件名:" + file.getName()); System.out.println("文件大小:" + file.length() + "字节"); } else { System.out.println("文件不存在"); } |
(2)创建与删除
方法 |
说明 |
createNewFile() |
创建新文件,返回 boolean(true 表示创建成功) |
mkdir() |
创建一级文件夹(父文件夹必须存在) |
mkdirs() |
创建多级文件夹(父文件夹不存在时自动创建,推荐使用) |
delete() |
删除文件或空文件夹,返回 boolean(删除后不进入回收站) |
示例:
// 创建文件 File file = new File("newFile.txt"); boolean isCreated = file.createNewFile(); System.out.println("文件创建成功?" + isCreated); // 创建多级文件夹 File dir = new File("dir1\\dir2\\dir3"); boolean isDirCreated = dir.mkdirs(); System.out.println("文件夹创建成功?" + isDirCreated); // 删除文件 boolean isDeleted = file.delete(); System.out.println("文件删除成功?" + isDeleted); |
(3)遍历文件夹
方法 |
说明 |
list() |
返回文件夹下所有一级文件 / 文件夹的名称数组(String[]) |
listFiles() |
返回文件夹下所有一级文件 / 文件夹的对象数组(File[],重点) |
listFiles() 注意事项:
- 若调用者是文件或路径不存在,返回 null;
- 若调用者是空文件夹,返回长度为 0 的数组;
- 若调用者是有内容的文件夹,返回一级文件 / 文件夹对象数组(包含隐藏文件);
- 若没有权限访问,返回 null。
示例:遍历文件夹下的所有文件
File dir = new File("D:\\test"); if (dir.isDirectory()) { File[] files = dir.listFiles(); if (files != null) { for (File f : files) { System.out.println(f.getAbsolutePath()); } } } |
三、递归算法:自调用的编程思想
递归是一种重要的算法思想,指方法在执行过程中调用自身的过程,广泛应用于文件夹遍历、树形结构处理等场景。
1. 递归的两种形式
- 直接递归:方法 A 直接调用方法 A 自身。
- 间接递归:方法 A 调用方法 B,方法 B 又调用方法 A(需谨慎使用,避免死循环)。
核心警告:递归必须有明确的终止条件,否则会导致「栈内存溢出错误」(递归死循环)。
2. 递归算法三要素
要写出正确的递归代码,需满足三个核心条件:
- 递归公式:将原问题分解为更小的子问题的数学关系(如 f(n) = n * f(n-1))。
- 终结点:递归停止的条件(最小子问题的解,如 f(1) = 1)。
- 方向:递归过程必须逐步靠近终结点(如 n 从大到小递减)。
3. 经典递归案例解析
(1)计算 n 的阶乘
阶乘定义:n! = 1 × 2 × 3 × ... × n,递归公式为 f(n) = n × f(n-1),终结点为 f(1) = 1。
public class FactorialDemo { public static void main(String[] args) { int result = factorial(5); System.out.println("5的阶乘:" + result); // 输出:120 } // 递归计算阶乘 public static int factorial(int n) { if (n == 1) { // 终结点 return 1; } else { return n * factorial(n - 1); // 递归公式,方向:n递减 } } } |
执行流程:从 factorial(5) 开始,逐步分解为 5×factorial(4)→5×4×factorial(3)→...→5×4×3×2×1,最终返回结果。
(2)递归求和(1~n 的和)
求和公式:f(n) = 1 + 2 + ... + n,递归公式为 f(n) = f(n-1) + n,终结点为 f(1) = 1。
public class SumDemo { public static void main(String[] args) { int sum = sum(5); System.out.println("1~5的和:" + sum); // 输出:15 } public static int sum(int n) { if (n == 1) { return 1; // 终结点 } else { return sum(n - 1) + n; // 递归公式 } } } |
(3)文件搜索(多级目录)
需求:在指定文件夹中搜索目标文件(如 test.txt),利用递归遍历所有子目录。
public class FileSearch { public static void main(String[] args) { searchFile(new File("D:\\test"), "test.txt"); } // 递归搜索文件 public static void searchFile(File dir, String target) { // 1. 校验目录合法性 if (dir == null || !dir.exists() || !dir.isDirectory()) { return; } // 2. 获取一级文件对象 File[] files = dir.listFiles(); if (files == null) { return; // 无权限或异常 } // 3. 遍历文件/文件夹 for (File file : files) { if (file.isFile()) { // 是文件,判断是否为目标 if (file.getName().equals(target)) { System.out.println("找到文件:" + file.getAbsolutePath()); } } else { // 是文件夹,递归搜索子目录 searchFile(file, target); } } } } |
四、字符集:字符与字节的映射规则
字符集(Charset)定义了字符与二进制字节之间的转换规则,是解决中文乱码问题的核心知识。
1. 常见字符集及特点
不同字符集对字符的编码方式不同,直接影响数据的存储和传输:
字符集 |
特点 |
适用场景 |
ASCII |
1 个字节存储 1 个字符,仅包含英文、数字和符号(共 128 个字符) |
纯英文环境 |
GBK |
中文占 2 字节,英文 / 数字占 1 字节,兼容 ASCII,包含 2 万 + 汉字 |
中文环境(如 Windows 系统默认) |
UTF-8 |
中文占 3 字节,英文 / 数字占 1 字节,兼容 ASCII,支持全球语言 |
国际化场景(推荐,如网页、跨平台项目) |
Unicode |
所有字符占 2 或 4 字节,不兼容 ASCII,是字符集标准而非编码方式 |
统一字符编码标准 |
2. 编码与解码
- 编码:将字符转换为字节(String → byte[]),通过 String 类的 getBytes() 方法实现。
- 解码:将字节转换为字符(byte[] → String),通过 String 类的构造器实现。
关键原则:编码与解码必须使用相同的字符集,否则会出现乱码。
3. Java 中的编码与解码操作
import java.io.UnsupportedEncodingException; public class CharsetDemo { public static void main(String[] args) throws UnsupportedEncodingException { String str = "我爱 Java"; // 编码:字符 → 字节 byte[] gbkBytes = str.getBytes("GBK"); // 使用 GBK 编码 byte[] utf8Bytes = str.getBytes("UTF-8"); // 使用 UTF-8 编码 // 解码:字节 → 字符 String gbkStr = new String(gbkBytes, "GBK"); // 正确解码(无乱码) String utf8Str = new String(utf8Bytes, "UTF-8"); // 正确解码(无乱码) // 错误示例:编码与解码字符集不一致,导致乱码 String errorStr = new String(gbkBytes, "UTF-8"); System.out.println("乱码结果:" + errorStr); } } |
注意:英文和数字在大多数字符集中编码规则一致(兼容 ASCII),因此很少出现乱码;中文乱码多因编码和解码字符集不一致导致。
五、IO 流:数据读写的核心工具
IO 流(Input/Output Stream)用于实现内存与外部设备(文件、网络等)之间的数据传输,是 Java 数据持久化的核心技术。
1. IO 流的分类
IO 流可按「数据流向」和「操作单位」分为四大类:
分类标准 |
类型 |
说明 |
数据流向 |
输入流 |
从外部设备读数据到内存(Input) |
输出流 |
从内存写数据到外部设备(Output) |
|
操作单位 |
字节流 |
以字节为单位读写数据,可操作所有类型文件(文本、图片、视频等) |
字符流 |
以字符为单位读写数据,仅适合操作文本文件(如 .txt、.java) |
2. 字节流:万能的文件操作工具
字节流以字节为单位处理数据,是最基础的 IO 流,可操作任意类型的文件。核心抽象类为 InputStream(输入)和 OutputStream(输出),常用实现类为 FileInputStream 和 FileOutputStream。
(1)文件字节输入流(FileInputStream)
用于从文件读取字节数据到内存,常用构造器和方法:
构造器 |
说明 |
FileInputStream(File file) |
基于文件对象创建输入流 |
FileInputStream(String path) |
基于文件路径创建输入流 |
核心方法 |
说明 |
read() |
读取 1 个字节,返回字节值(-1 表示读取结束) |
read(byte[] buffer) |
读取多个字节到缓冲区数组,返回实际读取的字节数(-1 表示结束) |
readAllBytes() |
一次性读取所有字节(JDK 9+,适合小文件) |
close() |
关闭流,释放资源 |
示例:读取文件内容
import java.io.FileInputStream; import java.io.IOException; public class FileInputStreamDemo { public static void main(String[] args) { // try-with-resource 语法:自动关闭流(资源需实现 AutoCloseable 接口) try (FileInputStream fis = new FileInputStream("test.txt")) { byte[] buffer = new byte[1024]; // 缓冲区(减少 IO 次数) int len; // 记录每次读取的字节数 // 循环读取,直到结束(len = -1) while ((len = fis.read(buffer)) != -1) { // 将字节转换为字符串(指定字符集避免乱码) System.out.print(new String(buffer, 0, len, "UTF-8")); } } catch (IOException e) { e.printStackTrace(); } } } |
(2)文件字节输出流(FileOutputStream)
用于从内存写入字节数据到文件,常用构造器和方法:
构造器 |
说明 |
FileOutputStream(File file) |
基于文件对象创建输出流(覆盖模式:新内容覆盖旧内容) |
FileOutputStream(String path, boolean append) |
基于路径创建输出流(append = true 为追加模式) |
核心方法 |
说明 |
write(int b) |
写入 1 个字节(取 int 低 8 位) |
write(byte[] buffer) |
写入整个字节数组 |
write(byte[] buffer, int off, int len) |
写入字节数组的一部分(从 off 开始,写 len 个字节) |
close() |
关闭流,释放资源 |
示例:写入数据到文件
import java.io.FileOutputStream; import java.io.IOException; public class FileOutputStreamDemo { public static void main(String[] args) { // 追加模式写入(第二个参数为 true) try (FileOutputStream fos = new FileOutputStream("output.txt", true)) { String data = "Hello, IO 流!\r\n"; // \r\n 是 Windows 换行符 // 将字符串编码为字节数组(指定 UTF-8 字符集) byte[] bytes = data.getBytes("UTF-8"); fos.write(bytes); // 写入字节数组 System.out.println("写入成功!"); } catch (IOException e) { e.printStackTrace(); } } } |
(3)文件复制案例
利用字节流实现任意文件的复制(核心逻辑:边读边写):
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class FileCopyDemo { public static void main(String[] args) { long start = System.currentTimeMillis(); // 记录开始时间 // 源文件和目标文件路径 String srcPath = "D:\\source.mp4"; String destPath = "D:\\copy.mp4"; try (FileInputStream fis = new FileInputStream(srcPath); FileOutputStream fos = new FileOutputStream(destPath)) { byte[] buffer = new byte[1024 * 8]; // 8KB 缓冲区(提高效率) int len; // 边读边写 while ((len = fis.read(buffer)) != -1) { fos.write(buffer, 0, len); // 写入读取到的有效字节 } } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("复制完成,耗时:" + (end - start) + " 毫秒"); } } |
3. 字符流:文本文件的最佳选择
字符流以字符为单位处理数据,专为文本文件设计,能自动处理字符编码问题(需确保编码一致)。核心抽象类为 Reader(输入)和 Writer(输出),常用实现类为 FileReader 和 FileWriter。
(1)文件字符输入流(FileReader)
用于从文本文件读取字符数据到内存,常用构造器和方法:
构造器 |
说明 |
FileReader(File file) |
基于文件对象创建输入流 |
FileReader(String path) |
基于文件路径创建输入流 |
核心方法 |
说明 |
read() |
读取 1 个字符,返回字符的 Unicode 码(-1 表示结束) |
read(char[] buffer) |
读取多个字符到缓冲区数组,返回实际读取的字符数(-1 表示结束) |
close() |
关闭流,释放资源 |
示例:读取文本文件
import java.io.FileReader; import java.io.IOException; public class FileReaderDemo { public static void main(String[] args) { try (FileReader fr = new FileReader("text.txt")) { char[] buffer = new char[1024]; // 字符缓冲区 int len; while ((len = fr.read(buffer)) != -1) { // 直接将字符数组转换为字符串(无需额外编码) System.out.print(new String(buffer, 0, len)); } } catch (IOException e) { e.printStackTrace(); } } } |
(2)文件字符输出流(FileWriter)
用于从内存写入字符数据到文本文件,常用构造器和方法:
构造器 |
说明 |
FileWriter(File file) |
基于文件对象创建输出流(覆盖模式) |
FileWriter(String path, boolean append) |
基于路径创建输出流(append = true 为追加模式) |
核心方法 |
说明 |
write(int c) |
写入 1 个字符(取 int 低 16 位) |
write(String str) |
写入字符串 |
write(char[] buffer) |
写入字符数组 |
flush() |
刷新缓冲区(将内存缓冲数据强制写入文件) |
close() |
关闭流(包含刷新操作) |
注意:字符流有缓冲区,数据会先存入内存缓冲区,需调用 flush() 或 close() 才能真正写入文件。
示例:写入文本文件
import java.io.FileWriter; import java.io.IOException; public class FileWriterDemo { public static void main(String[] args) { try (FileWriter fw = new FileWriter("output.txt", true)) { fw.write("字符流写入文本\n"); // 写入字符串 fw.write("Hello World!\n"); fw.flush(); // 手动刷新(可选,close() 会自动刷新) System.out.println("写入成功!"); } catch (IOException e) { e.printStackTrace(); } } } |
4. 缓冲流:提升 IO 性能的利器
缓冲流(Buffered Stream)是对原始流的包装,自带 8KB 缓冲区,通过减少物理 IO 操作次数大幅提高读写性能。
(1)缓冲字节流
- BufferedInputStream:包装字节输入流,提高读性能。
- BufferedOutputStream:包装字节输出流,提高写性能。
构造器:
// 包装字节输入流 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt")); // 包装字节输出流 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt")); |
示例:缓冲字节流复制文件
import java.io.*; public class BufferedStreamDemo { public static void main(String[] args) { try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src.mp4")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("dest.mp4"))) { byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } } catch (IOException e) { e.printStackTrace(); } } } |
(2)缓冲字符流
- BufferedReader:包装字符输入流,新增 readLine() 方法按行读取。
- BufferedWriter:包装字符输出流,新增 newLine() 方法跨平台换行。
构造器:
// 包装字符输入流 BufferedReader br = new BufferedReader(new FileReader("a.txt")); // 包装字符输出流 BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt")); |
新增方法:
- BufferedReader.readLine():读取一行文本(不含换行符),返回 null 表示结束。
- BufferedWriter.newLine():根据当前系统自动添加换行符(跨平台兼容)。
示例:按行读写文本
import java.io.*; public class BufferedCharStreamDemo { public static void main(String[] args) { try (BufferedReader br = new BufferedReader(new FileReader("source.txt")); BufferedWriter bw = new BufferedWriter(new FileWriter("dest.txt"))) { String line; // 按行读取 while ((line = br.readLine()) != null) { bw.write(line); // 写入一行 bw.newLine(); // 换行(跨平台) } } catch (IOException e) { e.printStackTrace(); } } } |
5. 其他常用流
(1)字符输入转换流(InputStreamReader)
解决字符流读取不同编码文本的乱码问题,可指定字符集编码:
import java.io.*; public class InputStreamReaderDemo { public static void main(String[] args) { try ( // 原始字节流 + 指定字符集 = 字符流(解决乱码) InputStreamReader isr = new InputStreamReader( new FileInputStream("gbk.txt"), "GBK"); BufferedReader br = new BufferedReader(isr) ) { String line; while ((line = br.readLine()) != null) { System.out.println(line); // 正确读取 GBK 编码文本 } } catch (IOException e) { e.printStackTrace(); } } } |
(2)打印流(PrintStream/PrintWriter)
提供便捷的打印功能,支持任意类型数据输出,自动刷新,适合日志输出:
类 |
特点 |
PrintStream |
继承自字节输出流,支持字节数据写入 |
PrintWriter |
继承自字符输出流,支持字符数据写入 |
常用方法:println(Xxx value) 打印任意类型数据并换行。
示例:使用 PrintWriter 写入日志
import java.io.*; public class PrintStreamDemo { public static void main(String[] args) { try ( // 指定字符集和自动刷新 PrintWriter pw = new PrintWriter( new FileOutputStream("log.txt"), true, "UTF-8") ) { pw.println("日志时间:2024-08-11"); pw.println("操作人:admin"); pw.println("操作结果:成功"); } catch (IOException e) { e.printStackTrace(); } } } |
(3)数据特殊流(DataInputStream/DataOutputStream)
可读写基本数据类型(如 int、double)和字符串,并保留数据类型信息:
类 |
作用 |
DataOutputStream |
写入基本类型和字符串(writeInt()、writeDouble()、writeUTF()) |
DataInputStream |
读取对应类型数据(readInt()、readDouble()、readUTF()) |
注意:读取顺序必须与写入顺序一致。
示例:读写基本类型数据
import java.io.*; public class DataStreamDemo { public static void main(String[] args) throws IOException { // 写入数据 try (DataOutputStream dos = new DataOutputStream( new FileOutputStream("data.dat"))) { dos.writeInt(100); // 写入 int dos.writeDouble(3.14); // 写入 double dos.writeUTF("Java IO"); // 写入字符串 } // 读取数据(顺序必须与写入一致) try (DataInputStream dis = new DataInputStream( new FileInputStream("data.dat"))) { int num = dis.readInt(); double d = dis.readDouble(); String str = dis.readUTF(); System.out.println(num + ", " + d + ", " + str); // 输出:100, 3.14, Java IO } } } |
6. 资源释放的两种方式
IO 流属于稀缺资源,使用后必须关闭以释放资源,常用两种方式:
(1)try-catch-finally(传统方式)
FileInputStream fis = null; try { fis = new FileInputStream("a.txt"); // 读取操作 } catch (IOException e) { e.printStackTrace(); } finally { // 确保关闭资源 if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } |
(2)try-with-resource(JDK 7+,推荐)
资源在 () 中声明,会自动关闭(需实现 AutoCloseable 接口):
try (FileInputStream fis = new FileInputStream("a.txt"); FileOutputStream fos = new FileOutputStream("b.txt")) { // 读写操作(无需手动关闭资源) } catch (IOException e) { e.printStackTrace(); } |
六、IO 框架:Commons-IO 简化开发
Apache Commons-IO 是一款常用的 IO 工具框架,封装了复杂的 IO 操作,提供简单易用的 API,能大幅提高开发效率。
1. 框架导入步骤
- 下载 Commons-IO 框架(如 commons-io-2.11.0.jar);
- 在项目中创建 lib 文件夹,将 jar 包复制到该文件夹;
- 右键 jar 包,选择 Add as Library 完成导入。
2. 核心工具类及常用方法
(1)FileUtils:文件操作工具类
import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; public class FileUtilsDemo { public static void main(String[] args) throws IOException { // 复制文件 FileUtils.copyFile(new File("a.txt"), new File("b.txt")); // 复制文件夹(含子内容) FileUtils.copyDirectory(new File("dir1"), new File("dir2")); // 读取文件内容 String content = FileUtils.readFileToString(new File("a.txt"), "UTF-8"); // 写入文件内容(追加模式) FileUtils.writeStringToFile( new File("b.txt"), "追加内容", "UTF-8", true); } } |
(2)IOUtils:流操作工具类
import org.apache.commons.io.IOUtils; import java.io.*; public class IOUtilsDemo { public static void main(String[] args) throws IOException { try (InputStream is = new FileInputStream("a.txt"); OutputStream os = new FileOutputStream("b.txt")) { // 复制流数据 IOUtils.copy(is, os); } } } |
总结
本文从数据存储方案入手,详细讲解了 File 类的文件操作、递归算法的核心思想、字符集的编码解码规则,以及 IO 流的完整体系(字节流、字符流、缓冲流等),最后介绍了 Commons-IO 框架的简化用法。
掌握这些知识后,你将能够:
- 使用 File 类创建、删除、遍历文件和文件夹;
- 运用递归算法解决复杂问题(如多级文件搜索);
- 理解字符集原理,避免中文乱码;
- 熟练使用各类 IO 流读写不同类型的文件;
- 利用框架简化 IO 操作,提高开发效率。
IO 流是 Java 开发的基础,建议通过大量实践加深理解,从简单的文件读写到复杂的项目应用,逐步积累经验。希望本文能帮助你快速掌握 Java 文件操作与 IO 流技术!
更多推荐
所有评论(0)