当前位置: 首页 > news >正文

(二十二)Java File类与IO流全面解析

一、Java IO体系概述

Java的IO(输入/输出)系统是Java语言中用于处理数据输入输出的核心API,它提供了丰富的类库来支持各种数据源和数据目标的读写操作。Java IO系统主要分为两大体系:

  1. 传统的IO系统(java.io包):基于流模型,提供同步阻塞式IO

  2. NIO系统(java.nio包及其子包):基于通道和缓冲区,提供非阻塞IO和选择器

本文将重点讲解传统的java.io包中的File类和IO流体系。

1.1 IO流的分类

Java IO流可以从多个维度进行分类:

按数据流向分

  • 输入流:从数据源读取数据(InputStream/Reader)

  • 输出流:向目标写入数据(OutputStream/Writer)

按处理数据类型分

  • 字节流:以字节为单位操作数据(InputStream/OutputStream)

  • 字符流:以字符为单位操作数据(Reader/Writer)

按功能分

  • 节点流:直接从数据源读写数据

  • 处理流:对现有流进行包装,提供额外功能

二、File类详解

File类是java.io包中代表文件和目录路径名的抽象表示,用于文件和目录的创建、删除、重命名、属性查询等操作。

2.1 File类核心构造方法

java

// 通过路径名创建File实例
File(String pathname)// 通过父路径和子路径创建
File(String parent, String child)// 通过父File对象和子路径创建
File(File parent, String child)// 通过URI创建
File(URI uri)

2.2 File类常用方法

2.2.1 文件和目录属性查询

java

boolean exists() // 文件或目录是否存在
boolean isFile() // 是否是文件
boolean isDirectory() // 是否是目录
boolean isHidden() // 是否是隐藏文件
long length() // 文件长度(字节数)
long lastModified() // 最后修改时间
String getName() // 获取文件名
String getPath() // 获取相对路径
String getAbsolutePath() // 获取绝对路径
String getCanonicalPath() throws IOException // 获取规范路径
2.2.2 文件和目录操作

java

boolean createNewFile() throws IOException // 创建新文件
boolean mkdir() // 创建单级目录
boolean mkdirs() // 创建多级目录
boolean delete() // 删除文件或空目录
boolean renameTo(File dest) // 重命名文件
2.2.3 目录内容查询

java

String[] list() // 返回目录中的文件和子目录名数组
File[] listFiles() // 返回目录中的文件和子目录File对象数组
String[] list(FilenameFilter filter) // 过滤后的文件名数组
File[] listFiles(FileFilter filter) // 过滤后的File对象数组

2.3 File类使用示例

2.3.1 文件基本操作

java

// 创建File对象
File file = new File("test.txt");// 检查文件是否存在
if (!file.exists()) {// 创建新文件boolean created = file.createNewFile();System.out.println("文件创建" + (created ? "成功" : "失败"));
}// 获取文件信息
System.out.println("文件名:" + file.getName());
System.out.println("绝对路径:" + file.getAbsolutePath());
System.out.println("文件大小:" + file.length() + "字节");
System.out.println("最后修改时间:" + new Date(file.lastModified()));// 删除文件
if (file.delete()) {System.out.println("文件删除成功");
}
2.3.2 目录操作示例

java

// 创建目录
File dir = new File("mydir");
if (!dir.exists()) {boolean created = dir.mkdir(); // 使用mkdirs()可创建多级目录System.out.println("目录创建" + (created ? "成功" : "失败"));
}// 列出目录内容
File[] files = dir.listFiles();
if (files != null) {System.out.println("目录内容:");for (File f : files) {System.out.println(f.getName() + (f.isDirectory() ? " [目录]" : " [文件]"));}
}// 递归遍历目录
public static void listAllFiles(File dir) {if (dir == null || !dir.exists()) return;if (dir.isFile()) {System.out.println(dir.getAbsolutePath());return;}for (File file : dir.listFiles()) {listAllFiles(file);}
}

三、字节流体系

字节流以字节为单位进行数据读写,适合处理二进制数据(如图片、音频、视频等)。

3.1 InputStream抽象类

InputStream是所有字节输入流的父类,定义了读取数据的基本方法:

java

// 读取一个字节,返回字节值(0-255),到达末尾返回-1
int read() throws IOException// 读取多个字节到字节数组b,返回实际读取的字节数
int read(byte[] b) throws IOException// 读取最多len个字节到数组b的off位置
int read(byte[] b, int off, int len) throws IOException// 跳过并丢弃n个字节数据
long skip(long n) throws IOException// 返回可读取的字节数估计值
int available() throws IOException// 关闭流释放资源
void close() throws IOException// 标记当前位置,readlimit指定标记有效范围
void mark(int readlimit)// 重置到最后一次mark的位置
void reset() throws IOException// 测试是否支持mark/reset
boolean markSupported()
3.1.1 FileInputStream

FileInputStream是文件字节输入流,用于从文件中读取数据。

java

// 构造方法
FileInputStream(String name) throws FileNotFoundException
FileInputStream(File file) throws FileNotFoundException
FileInputStream(FileDescriptor fdObj)// 使用示例
try (InputStream in = new FileInputStream("test.dat")) {byte[] buffer = new byte[1024];int bytesRead;while ((bytesRead = in.read(buffer)) != -1) {// 处理读取的数据}
} catch (IOException e) {e.printStackTrace();
}
3.1.2 ByteArrayInputStream

ByteArrayInputStream允许将字节数组作为输入源。

java

byte[] data = {65, 66, 67, 68, 69};
try (InputStream in = new ByteArrayInputStream(data)) {int c;while ((c = in.read()) != -1) {System.out.print((char)c); // 输出:ABCDE}
}
3.1.3 BufferedInputStream

BufferedInputStream是缓冲输入流,通过内部缓冲区减少实际IO操作次数,提高读取效率。

java

try (InputStream in = new BufferedInputStream(new FileInputStream("largefile.dat"))) {byte[] buffer = new byte[8192];int bytesRead;while ((bytesRead = in.read(buffer)) != -1) {// 处理数据}
}

3.2 OutputStream抽象类

OutputStream是所有字节输出流的父类,定义了写入数据的基本方法:

java

// 写入一个字节
void write(int b) throws IOException// 写入字节数组b
void write(byte[] b) throws IOException// 写入字节数组b中从off开始的len个字节
void write(byte[] b, int off, int len) throws IOException// 刷新输出流
void flush() throws IOException// 关闭流释放资源
void close() throws IOException
3.2.1 FileOutputStream

FileOutputStream是文件字节输出流,用于向文件写入数据。

java

// 构造方法
FileOutputStream(String name) throws FileNotFoundException
FileOutputStream(String name, boolean append) // append为true表示追加
FileOutputStream(File file)
FileOutputStream(File file, boolean append)
FileOutputStream(FileDescriptor fdObj)// 使用示例
try (OutputStream out = new FileOutputStream("output.dat")) {byte[] data = {72, 101, 108, 108, 111};out.write(data);out.flush(); // 确保数据写入磁盘
}
3.2.2 ByteArrayOutputStream

ByteArrayOutputStream将数据写入内部字节数组缓冲区。

java

try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {out.write("Hello".getBytes());byte[] result = out.toByteArray(); // 获取写入的数据System.out.println(new String(result)); // 输出:Hello
}
3.2.3 BufferedOutputStream

BufferedOutputStream是缓冲输出流,通过缓冲减少实际IO操作次数。

java

try (OutputStream out = new BufferedOutputStream(new FileOutputStream("largefile.dat"))) {for (int i = 0; i < 100000; i++) {out.write(i); // 写入操作会被缓冲}out.flush(); // 确保所有缓冲数据写入磁盘
}

3.3 字节流使用示例

3.3.1 文件复制

java

public static void copyFile(String src, String dest) throws IOException {try (InputStream in = new FileInputStream(src);OutputStream out = new FileOutputStream(dest)) {byte[] buffer = new byte[8192];int bytesRead;while ((bytesRead = in.read(buffer)) != -1) {out.write(buffer, 0, bytesRead);}out.flush();}
}
3.3.2 对象序列化

Java对象序列化需要使用ObjectOutputStream和ObjectInputStream。

java

// 序列化对象到文件
class Person implements Serializable {private String name;private int age;// 构造方法、getter、setter等
}Person person = new Person("张三", 25);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.dat"))) {out.writeObject(person);
}// 从文件反序列化对象
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.dat"))) {Person p = (Person) in.readObject();System.out.println(p.getName() + ", " + p.getAge());
}

四、字符流体系

字符流以字符为单位进行数据读写,适合处理文本数据,能够正确处理字符编码问题。

4.1 Reader抽象类

Reader是所有字符输入流的父类,定义了读取字符数据的基本方法:

java

// 读取单个字符,返回字符值,到达末尾返回-1
int read() throws IOException// 读取字符到字符数组cbuf
int read(char[] cbuf) throws IOException// 读取最多len个字符到数组cbuf的off位置
int read(char[] cbuf, int off, int len) throws IOException// 跳过n个字符
long skip(long n) throws IOException// 是否准备好读取
boolean ready() throws IOException// 关闭流
void close() throws IOException// 标记/重置相关方法
void mark(int readAheadLimit) throws IOException
void reset() throws IOException
boolean markSupported()
4.1.1 FileReader

FileReader是文件字符输入流,用于从文件读取文本数据。

java

// 构造方法
FileReader(String fileName) throws FileNotFoundException
FileReader(File file) throws FileNotFoundException
FileReader(FileDescriptor fd)// 使用示例
try (Reader reader = new FileReader("text.txt")) {char[] buffer = new char[1024];int charsRead;while ((charsRead = reader.read(buffer)) != -1) {System.out.print(new String(buffer, 0, charsRead));}
}
4.1.2 BufferedReader

BufferedReader是缓冲字符输入流,提供行读取功能。

java

try (BufferedReader reader = new BufferedReader(new FileReader("text.txt"))) {String line;while ((line = reader.readLine()) != null) { // 按行读取System.out.println(line);}
}
4.1.3 InputStreamReader

InputStreamReader是字节流到字符流的桥梁,可以指定字符编码。

java

try (InputStreamReader reader = new InputStreamReader(new FileInputStream("text.txt"), "UTF-8")) {char[] buffer = new char[1024];int charsRead;while ((charsRead = reader.read(buffer)) != -1) {System.out.print(new String(buffer, 0, charsRead));}
}

4.2 Writer抽象类

Writer是所有字符输出流的父类,定义了写入字符数据的基本方法:

java

// 写入单个字符
void write(int c) throws IOException// 写入字符数组
void write(char[] cbuf) throws IOException// 写入字符数组的部分内容
void write(char[] cbuf, int off, int len) throws IOException// 写入字符串
void write(String str) throws IOException// 写入字符串的部分内容
void write(String str, int off, int len) throws IOException// 追加字符序列
Writer append(CharSequence csq) throws IOException// 追加字符序列的部分内容
Writer append(CharSequence csq, int start, int end) throws IOException// 追加单个字符
Writer append(char c) throws IOException// 刷新流
void flush() throws IOException// 关闭流
void close() throws IOException
4.2.1 FileWriter

FileWriter是文件字符输出流,用于向文件写入文本数据。

java

// 构造方法
FileWriter(String fileName) throws IOException
FileWriter(String fileName, boolean append) // append为true表示追加
FileWriter(File file) throws IOException
FileWriter(File file, boolean append)
FileWriter(FileDescriptor fd)// 使用示例
try (Writer writer = new FileWriter("output.txt")) {writer.write("Hello, World!\n");writer.append("第二行内容");writer.flush();
}
4.2.2 BufferedWriter

BufferedWriter是缓冲字符输出流,提供高效的写入功能。

java

try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {writer.write("第一行");writer.newLine(); // 写入换行符writer.write("第二行");
}
4.2.3 OutputStreamWriter

OutputStreamWriter是字符流到字节流的桥梁,可以指定字符编码。

java

try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8")) {writer.write("中文字符测试");
}

4.3 字符流使用示例

4.3.1 文本文件复制

java

public static void copyTextFile(String src, String dest) throws IOException {try (BufferedReader reader = new BufferedReader(new FileReader(src));BufferedWriter writer = new BufferedWriter(new FileWriter(dest))) {String line;while ((line = reader.readLine()) != null) {writer.write(line);writer.newLine();}writer.flush();}
}
4.3.2 文件编码转换

java

public static void convertEncoding(String srcFile, String srcEncoding,String destFile, String destEncoding) throws IOException {try (InputStreamReader reader = new InputStreamReader(new FileInputStream(srcFile), srcEncoding);OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(destFile), destEncoding)) {char[] buffer = new char[1024];int charsRead;while ((charsRead = reader.read(buffer)) != -1) {writer.write(buffer, 0, charsRead);}writer.flush();}
}

五、IO流的高级应用

5.1 随机访问文件

RandomAccessFile类支持"随机访问"方式读写文件,可以任意移动文件指针。

java

// 构造方法
RandomAccessFile(String name, String mode) // mode: "r"只读,"rw"读写等
RandomAccessFile(File file, String mode)// 常用方法
long getFilePointer() // 返回当前指针位置
void seek(long pos) // 设置指针位置
int read() // 读取一个字节
void write(int b) // 写入一个字节
int read(byte[] b) // 读取到字节数组
void write(byte[] b) // 写入字节数组
long length() // 返回文件长度
void setLength(long newLength) // 设置文件长度// 使用示例:在文件中间插入数据
try (RandomAccessFile raf = new RandomAccessFile("data.txt", "rw")) {// 将指针移动到文件中间raf.seek(raf.length() / 2);// 保存指针后的内容byte[] remaining = new byte[(int)(raf.length() - raf.getFilePointer())];raf.readFully(remaining);// 移动指针回原位置raf.seek(raf.length() / 2);// 写入新内容raf.write("INSERTED TEXT".getBytes());// 追加之前保存的内容raf.write(remaining);
}

5.2 标准IO重定向

System类提供了三个静态字段用于标准IO操作:

java

System.in  // 标准输入流(InputStream)
System.out // 标准输出流(PrintStream)
System.err // 标准错误流(PrintStream)// 重定向示例
try {// 将标准输出重定向到文件PrintStream out = new PrintStream(new FileOutputStream("output.log"));System.setOut(out);// 将标准错误重定向到同一文件System.setErr(out);// 这些输出将被重定向到文件System.out.println("标准输出消息");System.err.println("错误消息");// 恢复标准输出System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
} catch (FileNotFoundException e) {e.printStackTrace();
}

5.3 进程IO

通过Process类可以与操作系统进程交互,获取其输入输出流。

java

try {// 启动进程Process process = Runtime.getRuntime().exec("ping www.baidu.com");// 获取进程输入流(进程的输出)try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {String line;while ((line = reader.readLine()) != null) {System.out.println(line);}}// 等待进程结束int exitCode = process.waitFor();System.out.println("进程退出码:" + exitCode);
} catch (IOException | InterruptedException e) {e.printStackTrace();
}

六、NIO简介

虽然本文主要讨论传统IO,但有必要简要介绍NIO(New IO)的基本概念,以便读者了解Java IO的发展。

6.1 NIO核心组件

  1. 通道(Channel):类似于流,但可以双向传输数据

    • FileChannel

    • SocketChannel

    • ServerSocketChannel

    • DatagramChannel

  2. 缓冲区(Buffer):数据容器,用于与通道交互

    • ByteBuffer

    • CharBuffer

    • IntBuffer等

  3. 选择器(Selector):用于多路复用IO

6.2 NIO与传统IO对比

特性传统IONIO
数据流单向流双向通道
缓冲部分支持(BufferedXX)必须使用缓冲区
阻塞模式阻塞IO支持非阻塞IO
多路复用不支持支持(Selector)
性能一般更高(减少系统调用)

6.3 简单NIO示例

java

// 使用FileChannel复制文件
try (FileInputStream fis = new FileInputStream("source.txt");FileOutputStream fos = new FileOutputStream("dest.txt");FileChannel source = fis.getChannel();FileChannel dest = fos.getChannel()) {ByteBuffer buffer = ByteBuffer.allocate(1024);while (source.read(buffer) != -1) {buffer.flip(); // 切换为读模式dest.write(buffer);buffer.clear(); // 清空缓冲区}
}

七、IO性能优化

7.1 选择合适的IO类

  • 处理文本数据:优先使用字符流

  • 处理二进制数据:使用字节流

  • 频繁读写:使用缓冲流

  • 随机访问:使用RandomAccessFile

7.2 合理设置缓冲区大小

缓冲区大小对IO性能有显著影响,一般建议:

  • 磁盘IO:8KB-32KB

  • 网络IO:1KB-8KB

java

// 设置缓冲区大小示例
int bufferSize = 32 * 1024; // 32KB
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream("largefile.dat"), bufferSize)) {// 读取操作
}

7.3 使用内存映射文件

对于大文件操作,可以使用内存映射文件提高性能。

java

try (RandomAccessFile raf = new RandomAccessFile("hugefile.dat", "rw");FileChannel channel = raf.getChannel()) {// 将文件映射到内存MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());// 直接操作缓冲区while (buffer.hasRemaining()) {byte b = buffer.get();// 处理数据}
}

7.4 减少IO操作次数

  • 批量读写代替单字节/字符操作

  • 使用BufferedXX类减少实际IO次数

  • 合理安排读写顺序(顺序读写通常比随机访问快)

八、常见问题与解决方案

8.1 文件编码问题

问题:读取文本文件时出现乱码

解决方案

  1. 明确知道文件编码时,使用InputStreamReader指定编码

  2. 不确定编码时,可以使用juniversalchardet等库检测编码

java

// 指定UTF-8编码读取文件
try (Reader reader = new InputStreamReader(new FileInputStream("text.txt"), "UTF-8")) {// 读取操作
}

8.2 资源泄露问题

问题:忘记关闭流导致资源泄露

解决方案

  1. 使用try-with-resources语法自动关闭资源

  2. 在finally块中手动关闭

java

// try-with-resources示例(推荐)
try (InputStream in = new FileInputStream("file.txt")) {// 使用流
} catch (IOException e) {e.printStackTrace();
}// 传统方式(不推荐)
InputStream in = null;
try {in = new FileInputStream("file.txt");// 使用流
} catch (IOException e) {e.printStackTrace();
} finally {if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}}
}

8.3 大文件处理问题

问题:处理大文件时内存不足

解决方案

  1. 使用缓冲流分块处理

  2. 使用NIO的FileChannel

  3. 使用内存映射文件

java

// 分块处理大文件示例
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream("largefile.dat"))) {byte[] buffer = new byte[8192]; // 8KB缓冲区int bytesRead;while ((bytesRead = in.read(buffer)) != -1) {// 处理每个块}
}

8.4 文件锁定问题

问题:多线程/多进程同时访问文件导致冲突

解决方案

  1. 使用FileLock实现文件锁定

  2. 使用同步机制控制访问

java

try (RandomAccessFile raf = new RandomAccessFile("file.txt", "rw");FileChannel channel = raf.getChannel()) {// 获取独占锁FileLock lock = channel.lock();try {// 执行文件操作} finally {lock.release();}
}

九、总结

Java的File类和IO流体系提供了强大而灵活的文件操作和IO处理能力。通过本文的学习,我们应该掌握:

  1. File类:文件和目录的创建、删除、属性查询等操作

  2. 字节流:InputStream/OutputStream体系及其实现类,适合处理二进制数据

  3. 字符流:Reader/Writer体系及其实现类,适合处理文本数据

  4. 高级特性:随机访问文件、标准IO重定向、进程IO等

  5. 性能优化:缓冲使用、NIO简介、内存映射文件等技术

  6. 常见问题:编码问题、资源泄露、大文件处理等解决方案

在实际开发中,应根据具体需求选择合适的IO类和优化策略,同时注意资源管理和异常处理,以构建健壮高效的IO处理代码。

相关文章:

  • 怎么样进行定量分析
  • 在 Java MyBatis 中遇到 “操作数类型冲突: varbinary 与 float 不兼容” 的解决方法
  • python打卡day30@浙大疏锦行
  • 团队氛围紧张,如何提升工作积极性?
  • RSA(公钥加密算法)
  • token令牌
  • Image and depth from a conventional camera with a coded aperture论文阅读
  • day30python打卡
  • FPGA:高速接口JESD204B以及FPGA实现
  • 动态IP技术在跨境电商中的创新应用与战略价值解析
  • Vant 使用整理
  • chrome源码中WeakPtr 跨线程使用详解:原理、风险与最佳实践
  • 一个专为 Windows 用户设计的配置文件集合提供类似 Unix 环境的美化和功能增强。
  • 【物联网】 ubantu20.04 搭建L2TP服务器
  • 【MCP】国内主流MCP服务全景解析:技术生态与未来趋势
  • 蓝桥杯分享经验
  • ROS2 话题 topic 节点间传递数据信息的桥梁
  • DRIVEGPT4: 通过大语言模型实现可解释的端到端自动驾驶
  • R语言数据可视化
  • 使用Python将 Excel 中的图表、形状和其他元素导出为图片
  • 一日双赛“莎头组合”赢得强势,但国乒已开始品尝输球滋味
  • 上海中心城区首条“定制化低空观光航线”启航,可提前一天提需求
  • 申伟强任上海申通地铁集团有限公司副总裁
  • 蔡建忠已任昆山市副市长、市公安局局长
  • 雅典卫城上空现“巨鞋”形状无人机群,希腊下令彻查
  • 高途一季度净利润同比增长1108%: “与吴彦祖一起学英语”短时间内就实现了盈利