File类
File类是Java中用于表示文件和目录路径的抽象表示,属于java.io包。它不直接操作文件内容,而是提供对文件系统中文件和目录的创建、删除、重命名、查询属性等操作的支持。
构造方法
- 通过字符串路径创建。
File file = new File("example.txt");- 通过父目录路径与子文件名组合创建。
File file2 = new File("/home/user/documents", "data.txt");- 通过父File对象与子文件名创建。
File file3 = new File(new File("/home/user"), "data.txt");路径区别
- 相对路径:相对于某个路径下,指明的路径
- 绝对路径:包含盘符在内的文件或文件目录路径
路径分隔符
- 在 Windows 系统中,路径分隔符为反斜杠(
\),例如:C:\Users\John\Documents\file.txt - 在 Unix、Linux 及 macOS 系统中,路径分隔符为正斜杠(
/),例如:/home/john/documents/file.txt
为了确保跨平台兼容性,File类提供了两个静态常量来表示路径分隔符:
File.separator:返回当前系统的默认路径分隔符字符(\或/)。File.separatorChar:返回当前系统的路径分隔符字符,类型为char。
String path = "folder" + File.separator + "subfolder" + File.separator + "file.txt"; System.out.println(path); // 输出根据系统自动调整的路径- 若处理路径字符串,可结合
Path和Paths工具类(Java 7+)进行更安全的操作
Path path = Paths.get("folder", "subfolder", "file.txt");常用方法
获取文件绝对路径
调用getAbsolutePath()方法可返回文件或目录的完整路径,包括驱动器、目录层级和文件名。
例如:
File file = new File("C:/Users/Example/Documents/test.txt"); System.out.println(file.getAbsolutePath()); // 输出: C:\Users\Example\Documents\test.txt获取文件路径
调用getPath()方法返回构造文件对象时传入的路径字符串,可能为相对路径或绝对路径。
例如:
File file = new File("data/file.txt"); System.out.println(file.getPath()); // 输出: data/file.txt获取文件名称
调用getName()方法返回文件或目录的名称(不含路径)。
例如:
File file = new File("/home/user/picture.jpg"); System.out.println(file.getName()); // 输出: picture.jpg获取上层目录路径
调用getParent()方法返回父目录路径,若文件位于根目录或无父目录,则返回null。
例如:
File file = new File("/var/log/app.log"); System.out.println(file.getParent()); // 输出: /var/log获取文件大小(字节数)
调用length()方法返回文件的大小(以字节为单位),仅适用于文件,不适用于目录。
例如:
File file = new File("document.pdf"); if (file.isFile()) { System.out.println("文件大小: " + file.length() + " 字节"); // 输出: 文件大小: 102400 字节 }获取最后修改时间
调用lastModified()方法返回文件最后一次修改的时间戳(毫秒值,从1970年1月1日开始计算)。可使用new Date()转换日期。
例如:
File file = new File("report.docx"); long lastModifiedTime = file.lastModified(); System.out.println("最后修改时间: " + lastModifiedTime); // 输出: 最后修改时间: 1719583200000列出目录下所有文件或目录名称
调用list()方法返回指定目录中所有子项的名称数组,若目录不存在或非目录则返回null。
例如:
File dir = new File("/home/user/documents"); String[] files = dir.list(); if (files != null) { for (String name : files) { System.out.println(name); } }获取目录下所有文件或目录的 File 对象数组
调用listFiles()方法返回指定目录中所有子项的File对象数组,若目录无效则返回null。
例如:
File dir = new File("/home/user/images"); File[] files = dir.listFiles(); if (files != null) { for (File f : files) { System.out.println(f.getName() + " - " + f.isFile()); } }重命名文件的renameTo方法
public boolean renameTo(File dest)File file1 = new File("hello.txt"); File file2 = new File("D:\\io\\hi.txt"); boolean renamed = file1.renameTo(file2); System.out.println(renamed);以file1.renameTo(file2)为例:
要确保该方法返回true,必须满足两个条件:
file1在硬盘中必须存在;file2在硬盘中不能已存在。
判断文件或目录是否存在及属性
File file2 = new File("C:/example/test.txt");检查是否为目录
boolean isDirectory = file2.isDirectory();若路径指向一个目录,则返回true;否则返回false。
检查是否为普通文件
boolean isFile = file2.isFile();若路径指向一个普通文件(非目录),则返回true;否则返回false。
检查文件或目录是否存在
boolean exists = file2.exists();若指定路径对应的文件或目录在系统中存在,则返回true;否则返回false。
检查是否可读
boolean canRead = file2.canRead();若当前用户有权限读取该文件或目录内容,则返回true;否则返回false。
检查是否可写
boolean canWrite = file2.canWrite();若当前用户有权限向该文件或目录写入数据,则返回true;否则返回false。
检查是否为隐藏文件或目录
boolean isHidden = file2.isHidden();若文件或目录在操作系统中被标记为隐藏(如 Windows 中的属性为隐藏),则返回true;否则返回false。
创建文件
调用createNewFile()方法可创建一个新文件。该方法仅在目标文件不存在时创建,并返回true表示创建成功;若文件已存在,则不进行任何操作,返回false。
File file = new File("example.txt"); boolean created = file.createNewFile(); if (created) { System.out.println("文件创建成功"); } else { System.out.println("文件已存在或创建失败"); }创建单层目录
调用mkdir()方法用于创建一个目录。仅当父目录存在且目标目录不存在时,才会创建该目录,返回true;若目录已存在或父目录不存在,则返回false。
File dir = new File("mydir"); boolean created = dir.mkdir(); if (created) { System.out.println("目录创建成功"); } else { System.out.println("目录创建失败:可能已存在或父目录不存在"); }创建多级目录
调用mkdirs()方法可创建包含多级嵌套的目录结构。如果父目录不存在,会自动递归创建所有必要的上级目录。返回true表示创建成功,即使目录已存在也返回true。
File nestedDir = new File("parent/child/grandchild"); boolean created = nestedDir.mkdirs(); if (created) { System.out.println("多级目录创建成功"); } else { System.out.println("目录创建失败"); }删除文件
Java中的File类提供了delete()方法,用于删除指定的文件或目录,删除不走回收站,返回一个布尔值:
true表示文件或目录已被成功删除。false表示删除失败,可能原因包括:文件被占用、权限不足、路径不存在、目录非空等。
boolean deleted = file.delete(); if (deleted) { System.out.println("删除成功"); } else { System.out.println("删除失败"); }IO流概述
IO流(Input/Output Stream)是用于处理数据输入与输出的机制,主要通过java.io包中的类来实现。主要基于字节和字符两种基本单位。它提供了统一的接口来读取和写入数据,支持文件、网络、内存等多种数据源。
IO流的分类方式
以程序为中心,根据数据流动方向,可分为:
- 输入流:从外部源读取数据到程序中,常用抽象类为
InputStream(字节输入流)和Reader(字符输入流)。 - 输出流:将程序中的数据写入外部目标,常用抽象类为
OutputStream(字节输出流)和Writer(字符输出流)。
根据处理的数据类型,可分为:
- 字节流:以字节为单位进行数据传输,适用于处理二进制数据,如图片、音频、视频等。典型类包括
FileInputStream、FileOutputStream、ByteArrayInputStream等。 - 字符流:以字符为单位进行数据传输,主要用于文本数据的读写,自动处理字符编码转换。典型类包括
FileReader、FileWriter、BufferedReader、BufferedWriter等。
根据是否直接连接数据源,可分为:
- 节点流:直接与数据源或数据目标相连的流,用于读取或写入原始数据。常见的节点流包括文件流、字节数组流、字符数组流等。
FileInputStream和FileOutputStream是典型的节点流,它们直接操作文件系统中的数据。 - 处理流:处理流不直接连接数据源,而是包装一个节点流,提供额外的处理能力,增强节点流的功能,如缓冲、转换编码、过滤数据等。
BufferedInputStream和BufferedOutputStream是处理流,它们通过缓冲机制提高读写效率。
| 【抽象基类】 | 字节流 | 字符流 |
| 输入流 | InputStream | Reader |
| 输出流 | OutputStream | Writer |
IO流40多个类均由这四个抽象基类派生,子类名称都是以其父类名作为子类名后缀。
使用基本步骤
1.指定文件路径:既能使用 NIO 的Paths.get()构造Path对象,它能自动处理不同操作系统的路径分隔符。也可以用传统的File对象。
2.创建节点流:
- 字符节点流:
FileReader、FileWriter、BufferedReader、BufferedWriter等。 - 字节节点流:
FileInputStream、FileOutputStream等。
3.包装缓冲流:缓冲流属于“处理流”,职责是装饰节点流,增加缓冲功能,减少底层系统调用。在使用时,可以将节点流包装为缓冲流,读写性能会大幅提升。
4.执行读写操作:
字节流:使用自定义
byte[]数组作为临时缓冲区,配合read(byte[], int, int)和write(byte[], int, int)进行块读写,避免单字节循环。字符流:用
readLine()逐行读取,用write()写入并换行。
5.关闭流资源:强制使用try-with-resources(JDK 7+)语法。这样,无论代码正常执行还是抛异常,所有AutoCloseable资源都会自动按逆序关闭。在循环中创建流时,每次迭代都要创建新的 try-with-resources 块;若流作为返回值或参数传递,也必须明确关闭责任。
举例:使用缓冲流复制字符流文件内容
// 定义源文件对象,指定路径为 "src.txt" File srcfile = new File("src.txt"); // 定义目标文件对象,指定路径为 "dest.txt" File destfile = new File("dest.txt"); // 创建文件读取流,用于从源文件读取数据 FileReader fr = new FileReader(srcfile); // 创建文件写入流,用于向目标文件写入数据 FileWriter fw = new FileWriter(destfile); // 包装读取流,提升读取效率,支持按行读取 BufferedReader br = new BufferedReader(fr); // 包装写入流,提升写入效率,支持缓冲写入 BufferedWriter bw = new BufferedWriter(fw); // 定义字符数组,用于临时存储读取的数据,大小为10个字符 char[] arr = new char[10]; // 定义变量用于记录每次读取的实际字符数 int len; // 循环读取源文件内容,直到读取结束(返回-1表示读完) while ((len = br.read(arr)) != -1) { // 将读取到的字符数据写入目标文件,从索引0开始,写入len个字符 bw.write(arr, 0, len); } // 关闭缓冲读取流,释放资源 br.close(); // 关闭缓冲写入流,确保所有数据被写入文件并释放资源 bw.close();try-catch-finally优化后的代码
// 定义输入缓冲读取器,用于从源文件读取数据 BufferedReader br = null; // 定义输出缓冲写入器,用于向目标文件写入数据 BufferedWriter bw = null; try { // 指定源文件路径为 "src.txt" File srcfile = new File("src.txt"); // 指定目标文件路径为 "dest.txt" File destfile = new File("dest.txt"); // 创建文件读取器,关联源文件 FileReader fr = new FileReader(srcfile); // 创建文件写入器,关联目标文件(覆盖写入) FileWriter fw = new FileWriter(destfile); // 将文件读取器包装为缓冲读取器,提升读取效率 br = new BufferedReader(fr); // 将文件写入器包装为缓冲写入器,提升写入效率 bw = new BufferedWriter(fw); // 定义字符数组,作为读取数据的缓冲区,大小为 10 个字符 char[] arr = new char[10]; // 记录每次读取的实际字符数量 int len; // 循环读取源文件内容,直到读取结束(返回 -1 表示读完) while ((len = br.read(arr)) != -1) { // 将读取到的数据写入目标文件,写入范围为 [0, len) bw.write(arr, 0, len); } // 刷新缓冲区,确保所有数据被写入文件 bw.flush(); } catch (IOException e) { // 捕获可能发生的 I/O 异常,并将其包装为运行时异常抛出 throw new RuntimeException("文件读写过程中发生错误", e); } finally { // 确保资源正确关闭,避免资源泄漏 // 若缓冲读取器已创建,则尝试关闭 if (br != null) { try { br.close(); } catch (IOException e) { // 忽略关闭失败异常,或可选择记录日志 throw new RuntimeException("关闭输入流时发生错误", e); } } // 若缓冲写入器已创建,则尝试关闭 if (bw != null) { try { bw.close(); } catch (IOException e) { // 忽略关闭失败异常,或可选择记录日志 throw new RuntimeException("关闭输出流时发生错误", e); } } }try-with-resources优化后的代码
// 定义源文件和目标文件路径 File srcFile = new File("src.txt"); File destFile = new File("dest.txt"); // 使用 try-with-resources 语法自动管理资源,避免手动关闭流 try (BufferedReader reader = new BufferedReader(new FileReader(srcFile)); BufferedWriter writer = new BufferedWriter(new FileWriter(destFile))) { // 定义字符缓冲数组,大小为 1024 字符,提升读写效率 char[] buffer = new char[1024]; int bytesRead; // 循环读取源文件内容,每次读取最多 1024 个字符 while ((bytesRead = reader.read(buffer)) != -1) { // 将读取到的字符写入目标文件,写入长度为实际读取的字符数 writer.write(buffer, 0, bytesRead); } // 文件复制完成,无需额外操作,资源会自动关闭 } catch (IOException e) { // 捕获并处理可能出现的 I/O 异常,抛出运行时异常以中断程序 throw new RuntimeException("文件复制过程中发生错误", e); }FileReader
try (FileReader fr = new FileReader("hello.txt")) { int len; while ((len = fr.read()) != -1) { System.out.println((char) len); } } catch (IOException e) { System.err.println("读取文件失败: " + e.getMessage()); }FileWriter
try (FileWriter fw = new FileWriter("hello.txt")) { fw.write("Hello, world!"); // 无需手动关闭,try-with-resources 会自动处理 } catch (IOException e) { System.err.println("写入文件失败: " + e.getMessage()); // 推荐做法:记录日志,而非使用 e.printStackTrace() }FileInputStream
import java.io.FileInputStream; import java.io.IOException; public class ReadFileExample { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("example.txt")) { int byteData; while ((byteData = fis.read()) != -1) { System.out.print((char) byteData); } } catch (IOException e) { e.printStackTrace(); } } }FileOutputStream
import java.io.FileOutputStream; import java.io.IOException; public class WriteFileExample { public static void main(String[] args) { String content = "Hello, this is a test file written in Java!"; try (FileOutputStream fos = new FileOutputStream("output.txt")) { byte[] bytes = content.getBytes(); fos.write(bytes); } catch (IOException e) { e.printStackTrace(); } } }使用 FileInputSteam 与 FileOutputStream 实现文件复制
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class FileCopyExample { public static void main(String[] args) { String sourcePath = "source.txt"; String destinationPath = "copy.txt"; try (FileInputStream fis = new FileInputStream(sourcePath); FileOutputStream fos = new FileOutputStream(destinationPath)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); } System.out.println("文件复制完成:从 " + sourcePath + " 到 " + destinationPath); } catch (IOException e) { System.err.println("文件操作失败:" + e.getMessage()); } } }缓冲流
缓冲流是Java I/O体系中用于提高读写效率的一类流,其核心机制是在内存中设置一个临时缓冲区,减少对底层设备(如文件、网络)的频繁访问。通过批量读写操作,显著降低系统调用次数,提升性能。
常见的缓冲流类型
- BufferedInputStream:字节缓冲输入流,用于包装其他字节输入流,提供缓冲功能。
- BufferedOutputStream:字节缓冲输出流,用于包装其他字节输出流,支持缓冲写入。
- BufferedReader:字符缓冲输入流,可读取文本数据,并支持按行读取。
- BufferedWriter:字符缓冲输出流,用于高效写入字符数据,支持换行符自动处理。
转换流
转换流是Java中用于处理字符编码转换的输入输出流,主要作用是在字节流和字符流之间进行桥梁连接。它们能够将字节数据按照指定的字符编码格式转换为字符数据,或反之。
分类
1. InputStreamReader
- 作用:将字节输入流转换为字符输入流。
- 构造方法:
InputStreamReader(InputStream in) InputStreamReader(InputStream in, String charsetName)
2. OutputStreamWriter
- 作用:将字符输出流转换为字节输出流。
- 构造方法:
OutputStreamWriter(OutputStream out) OutputStreamWriter(OutputStream out, String charsetName)
实际应用示例
// 读取文件并按UTF-8编码解析 try (FileInputStream fis = new FileInputStream("input.txt"); InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); BufferedReader br = new BufferedReader(isr)) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } // 写入文件并指定编码 try (FileOutputStream fos = new FileOutputStream("output.txt"); OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8"); BufferedWriter bw = new BufferedWriter(osw)) { bw.write("你好,世界!"); bw.newLine(); } catch (IOException e) { e.printStackTrace(); }
- 必须明确指定字符编码,否则可能使用系统默认编码,导致跨平台兼容性问题。
- 推荐始终显式指定编码,如“UTF-8”或“GBK”,以确保程序行为一致。
- 转换流不支持直接操作字符数组或字符串,需结合其他流类使用。
常见编码格式对比
| 编码 | 特点 | 适用场景 |
|---|---|---|
| UTF-8 | 支持全球字符,兼容性强 | 网络传输、现代应用 |
| GBK/GB2312 | 中文支持良好 | 国内旧系统或中文文档 |
| ISO-8859-1 | 单字节编码,仅支持西欧字符 | 简单文本处理 |
常见用途
- 当读取或写入文件时,若文件使用特定编码(如GBK、UTF-8),需通过转换流指定编码方式,避免乱码。
- 可以配合缓冲流使用,提高性能,例如
BufferedReader与InputStreamReader组合,或BufferedWriter与OutputStreamWriter组合。
数据流
数据流是专门用于读写基本数据类型的流,支持直接读写int、double、boolean等原始类型数据。
分类
数据输入流:
DataInputStream- 支持方法:
readInt():读取一个整数(4字节)readDouble():读取一个双精度浮点数(8字节)readBoolean():读取布尔值readUTF():读取字符串(使用UTF-8编码)
- 支持方法:
数据输出流:
DataOutputStream- 支持方法:
writeInt(int v):写入一个整数writeDouble(double v):写入一个双精度浮点数writeBoolean(boolean b):写入布尔值writeUTF(String str):写入字符串(以UTF-8编码存储)
- 支持方法:
使用示例:数据流写入与读取
import java.io.*; public class DataStreamExample { public static void main(String[] args) { try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.dat")); DataInputStream dis = new DataInputStream(new FileInputStream("data.dat"))) { // 写入数据 dos.writeInt(100); dos.writeDouble(3.14159); dos.writeBoolean(true); dos.writeUTF("Hello, Data Stream!"); // 读取数据 int num = dis.readInt(); double pi = dis.readDouble(); boolean flag = dis.readBoolean(); String text = dis.readUTF(); System.out.println("读取结果:"); System.out.println("整数:" + num); System.out.println("浮点数:" + pi); System.out.println("布尔值:" + flag); System.out.println("字符串:" + text); } catch (IOException e) { e.printStackTrace(); } } }如果文件较大,可以用BufferedInputStream/BufferedOutputStream缓冲流包装底层节点流,以提升性能。
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("data.bin")));序列化与反序列化:ObjectOutputStream / ObjectInputStream
序列化(Serialization)是指将内存中的 Java 对象转换为字节流,以便存储到文件、数据库或通过网络传输;反序列化(Deserialization)则是将字节流重新还原为 Java 对象的过程。Java 提供了ObjectOutputStream和ObjectInputStream两个对象流类来分别实现这两项操作。
ObjectOutputStream:序列化对象
ObjectOutputStream是字节输出流,用于将对象写入底层输出流中。核心方法是writeObject()。
// 创建对象输出流并关联到文件 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat")); // 将 Person 对象序列化写入文件 oos.writeObject(person); // 关闭流以释放资源 oos.close();ObjectInputStream:反序列化对象
ObjectInputStream是字节输入流,用于从底层输入流中读取字节并还原为 Java 对象。核心方法是readObject()。
// 创建对象输入流并关联到文件 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat")); // 读取对象并强制转换为 Person 类型 Person person = (Person) ois.readObject(); // 关闭流 ois.close();反序列化过程:通过ObjectInputStream类将字节流转换为对象进行反序列化。readObject()方法会读取序列化时的对象状态数据,并通过ClassNotFoundException抛出类缺失异常,因此调用处需要捕获该异常。
import java.io.*; public class TestSerialization { public static void main(String[] args) { Person person = new Person(121, "Albert", 18); // 序列化 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) { oos.writeObject(person); System.out.println("对象已序列化到 person.dat"); } catch (IOException e) { e.printStackTrace(); } // 反序列化 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) { Person saved = (Person) ois.readObject(); System.out.println("反序列化结果:" + saved); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } class Person implements Serializable { private String name; private int age; private int ID; public Person(int ID, String name, int age) { this.ID = ID; this.name = name; this.age = age; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + ", ID=" + ID + "}"; } }
须实现
Serializable接口如果对象所属类未实现
java.io.Serializable,直接写出对象会抛出NotSerializableException。若类中包含引用类型的字段,该引用类型也必须实现Serializable接口,否则序列化过程中遇到该字段时同样会抛异常。声明
serialVersionUID建议为每个可序列化类显式声明
serialVersionUID静态常量,例如private static final long serialVersionUID = 1L;。如果省略,JVM 会根据类结构自动生成,但当类结构发生变化时,自动生成的 UID 可能改变,导致序列化版本不一致而抛出InvalidClassException。
transient关键字被
transient修饰的字段不会被序列化。通常用来过滤敏感信息(如密码)或不需要持久化的缓存字段。
static字段静态字段属于类级别,不会被序列化。因为序列化保存的是“对象状态”,而不是“类状态”。
打印流
PrintStream
PrintStream继承自OutputStream,属于字节输出流体系,它可以将各种数据类型(如int、double、字符串等)自动转换成字节并输出
import java.io.*; public class testPrint { public static void main(String[] args) throws FileNotFoundException { // 将 System.out 保存为原始输出流对象,便于后续恢复 PrintStream originalOut = System.out; // 1. 使用默认的 System.out 打印信息到控制台 originalOut.println("打印流"); // 输出字符串 "打印流" originalOut.println(true); // 输出布尔值 true // 2. 创建一个新的 PrintStream,输出目标为文件 "println.txt" // 包装顺序:FileOutputStream -> BufferedOutputStream -> PrintStream // true 参数表示自动刷新缓冲区(每次 println 后立即写入) PrintStream ps = new PrintStream( new BufferedOutputStream( new FileOutputStream("println.txt") ), true ); // 3. 使用新创建的 PrintStream 打印内容到文件 ps.println("打印流"); // 写入文件:打印流 ps.println(true); // 写入文件:true // 4. 将 System.out 重定向到新创建的 PrintStream(即文件) System.setOut(ps); // 5. 此时所有通过 System.out 输出的内容都会被写入文件 System.out.println("这条信息会写入文件"); // 写入文件,而非控制台 // 6. 恢复 System.out 到原始控制台输出 // 使用系统标准输出文件描述符(FileDescriptor.out)重新创建一个 PrintStream System.setOut(new PrintStream( new BufferedOutputStream( new FileOutputStream(FileDescriptor.out) ), true )); // 7. 恢复后,再次使用 System.out 打印,输出将回到控制台 System.out.println("这条信息会写入控制台"); // 输出到控制台 } }关键点解析
- PrintStream:用于格式化输出数据,支持多种类型(如字符串、布尔值、数字等)。
- FileOutputStream("println.txt"):创建文件输出流,向文件写入字节数据。
- BufferedOutputStream:提高写入效率,减少 I/O 次数。
- System.setOut(PrintStream):改变标准输出流的目标位置,实现重定向。
- FileDescriptor.out:代表系统标准输出设备(通常是控制台),用于恢复输出。
- true 作为构造参数:启用自动刷新,确保每次调用
println()都立即写入底层输出流。
注意事项
- 程序运行后会在当前目录生成文件
println.txt,内容包含两次println输出。 - 若未正确处理异常,可能抛出
FileNotFoundException,因此方法声明中包含throws FileNotFoundException。 - 重定向操作是全局性的,影响整个 JVM 中所有使用
System.out的地方。 - 建议在程序结束前恢复输出流,避免影响其他组件或测试结果。
运行结果示例
控制台输出:
打印流 true 这条信息会写入控制台文件println.txt内容:
打印流 true 这条信息会写入文件标准错误流
标准错误流通过System.err提供,同样是PrintStream类型,用于输出错误信息。虽然与System.out使用相同的底层机制,但通常被独立用于错误日志或调试信息。
System.err.println("发生了一个错误!");在命令行中,System.out和System.err可以被分别重定向,便于区分正常输出和错误输出。
随机访问流
RandomAccessFile是 Java 中专门用于随机访问文件的类。它与其他 IO 流不同,既可以读取文件,也可以写入文件,并且可以通过移动内部指针(Pointer)定位到文件的任意位置进行操作5。它继承自Object,实现了DataInput和DataOutput接口,因此可以读写基本数据类型和 UTF 字符串
构造方法
public RandomAccessFile(String name, String mode) public RandomAccessFile(File file, String mode)mode常用的取值有:
“r”:只读模式,只能读取文件,文件不存在则抛异常。
“rw”:读写模式,可读可写,文件不存在会自动创建。
“rwd”:读写模式,同步文件内容的更新。
“rws”:读写模式,同步文件内容和元数据的更新。
RandomAccessFile raf = new RandomAccessFile("example.txt", "rw");随机读取数据
通过seek()定位到指定字节位置后,可进行读取操作。
raf.seek(100); // 移动指针到第100个字节处 int data = raf.read(); // 读取一个字节随机写入数据
可在任意位置写入数据,原有内容可能被覆盖。
raf.seek(50); raf.write('X'); // 写入字符'X'到第50字节位置获取当前文件指针位置
long currentPosition = raf.getFilePointer();获取文件长度
long fileLength = raf.length();举例
import java.io.RandomAccessFile; import java.io.IOException; public class RandomAccessFileDemo { public static void main(String[] args) { String filePath = "test.txt"; try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) { // 1. 写入数据(初始指针位于文件开头) raf.writeUTF("Hello RandomAccessFile"); raf.writeInt(100); raf.writeLong(20260105L); // 2. 获取当前指针位置 long currentPos = raf.getFilePointer(); System.out.println("写入后指针位置:" + currentPos); // 3. 将指针重置到文件开头 raf.seek(0); // 4. 按照写入顺序读取数据(类型需匹配) String str = raf.readUTF(); int num = raf.readInt(); long date = raf.readLong(); System.out.println("读取的字符串:" + str); System.out.println("读取的int值:" + num); System.out.println("读取的long值:" + date); // 5. 定位到指定位置,覆盖写入 raf.seek(str.length() + 2); // UTF字符串前有2字节长度信息,需跳过 raf.writeUTF("Java"); } catch (IOException e) { e.printStackTrace(); } } }UTF 字符串的长度前缀:
writeUTF()会先写入 2 字节的长度标识,因此使用seek()定位时需要额外考虑这 2 个字节的偏移