黑马JAVA+AI 加强09-2 IO流-文件字节输入流-文件字节输出流-文件字符流-文件字符输出流
1.输入流-输出流

1.1文件字节输入流-适合文件复制


1.2 代码案例
public class FileInputStreamDemo2 {public static void main(String[] args) throws Exception {// 目标:掌握文件字节输入流读取文件中的字节数组到内存中来。// 1、创建文件字节输入流管道于源文件接通InputStream is = new FileInputStream("day03-file-io\\src\\dlei02.txt"); // 2、开始读取文件中的字节并输出: 每次读取一个字节// 定义一个变量记住每次读取的一个字节int b;while ((b = is.read()) != -1) {System.out.print((char) b);}1.性能问题:每次只读取 1 个字节,需要频繁与磁盘交互,对于大文件来说效率极低。2.乱码问题:字节流读取汉字时一定会乱码。因为汉字在 UTF-8、GBK 等编码中通常占用 2-3 个字节,而read()每次只读取 1 个字节,无法完整解析汉字的编码,导致输出乱码。}
}
改进方向 1:使用字节数组批量读取(提升性能,但仍可能乱码)
- 通过定义字节数组作为缓冲区,一次读取多个字节,减少 IO 交互次数,提升性能。
public class FileInputStreamImproved1 {public static void main(String[] args) throws Exception {// 1. 创建文件字节输入流,连接目标文件InputStream is = new FileInputStream("day03-file-io\\src\\dlei02.txt");// 2. 定义字节数组作为缓冲区(长度建议为1024的倍数,如1024、2048等)byte[] buffer = new byte[1024];// 记录每次读取的有效字节数(避免读取到缓冲区中残留的旧数据)int len;// 3. 循环读取:每次读取最多buffer.length个字节,存入buffer,返回实际读取的字节数while ((len = is.read(buffer)) != -1) {// 将字节数组转换为字符串(需指定编码,否则使用系统默认编码,仍可能乱码)System.out.print(new String(buffer, 0, len, "UTF-8"));}// 4. 关闭流(释放资源,实际开发中建议用try-with-resources自动关闭)is.close();但字节流本身不处理编码,读取中文仍可能乱码(适合读取二进制文件,如图片、视频等)。}
}

1.3一次性读完,可以避免中文乱码,但也不是什么好办法

2.文件字节输出流

2.1 文件字节输出流构造器和方法

案例 1:基本使用(覆盖写入单个字节 / 字节数组)
public class FileOutputStreamDemo1 {public static void main(String[] args) throws Exception {// 1. 创建输出流管道(文件不存在会自动创建;存在则覆盖原内容)OutputStream os = new FileOutputStream("output1.txt");// 2. 写入数据// 方式1:写单个字节(只取int的低8位作为字节)os.write(97); // 写入'a'(ASCII码97)os.write(10); // 写入换行符'\n'// 方式2:写字节数组(适合批量写入,效率高)byte[] data = "Hello, FileOutputStream!".getBytes(); // 字符串转字节数组(默认编码)os.write(data);// 3. 关闭流(释放资源,必须执行)os.close();System.out.println("写入完成!");}
}
案例 2:追加写入(不覆盖原有内容)
public class FileOutputStreamDemo2 {public static void main(String[] args) throws Exception {// 1. 创建输出流管道(第二个参数为true表示追加模式)OutputStream os = new FileOutputStream("output2.txt", true);// 2. 追加写入数据byte[] appendData = "\n这是追加的内容!".getBytes("UTF-8"); // 指定编码避免中文乱码os.write(appendData);// 3. 关闭流os.close();System.out.println("追加写入完成!");}
}
案例 3:写入字节数组的一部分
public class FileOutputStreamDemo3 {public static void main(String[] args) throws Exception {OutputStream os = new FileOutputStream("output3.txt");byte[] data = "abcdefghijk".getBytes();// 写入数组从索引2开始,长度为4的部分(即"cdef")os.write(data, 2, 4);os.close();System.out.println("部分写入完成!");}
}
案例 4:结合 try-with-resources 自动关闭流(推荐)
public class FileOutputStreamDemo4 {public static void main(String[] args) {// try-with-resources:流会自动关闭,无需手动调用close()try (OutputStream os = new FileOutputStream("output4.txt")) {String content = "使用try-with-resources更安全!\n中文也能正常写入~";os.write(content.getBytes("UTF-8")); // 显式指定编码,避免中文乱码System.out.println("自动关闭流模式写入完成!");} catch (Exception e) {e.printStackTrace();}}
}
2.2 文件复制案例

- 以下是基于字节流实现文件复制的代码,通过FileInputStream读取源文件字节,再用FileOutputStream写入目标文件,适用于任何类型的文件(图片、文档、视频等):
代码1:不推荐-太臃肿

public class FileCopyDemo {public static void main(String[] args) {// 源文件路径(需要复制的文件)String sourcePath = "D:\\original.jpg";// 目标文件路径(复制后的文件)String targetPath = "C:\\copy.jpg";// 定义输入流、输出流变量InputStream is = null;OutputStream os = null;try {// 1. 创建字节输入流(连接源文件)和字节输出流(连接目标文件)is = new FileInputStream(sourcePath);os = new FileOutputStream(targetPath);// 2. 定义字节数组作为缓冲区(提升复制效率,建议1024的倍数)byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区int len; // 记录每次读取的有效字节数// 3. 循环读取源文件字节,并写入目标文件// 每次读取buffer长度的字节,存入buffer,返回实际读取的字节数(-1表示读取完毕)while ((len = is.read(buffer)) != -1) {// 将缓冲区中的字节写入目标文件(只写有效长度的字节)os.write(buffer, 0, len);}System.out.println("文件复制成功!");} catch (Exception e) {e.printStackTrace();} finally {// 4. 关闭流(先关输出流,再关输入流,避免资源泄漏)try {if (os != null) os.close();if (is != null) is.close();} catch (Exception e) {e.printStackTrace();}}}
}
代码2:推荐 简化版本(try-with-resources 自动关流):

public class FileCopySimple {public static void main(String[] args) {String sourcePath = "D:\\original.jpg";String targetPath = "C:\\copy.jpg";// try-with-resources自动关闭流(无需手动调用close())try (InputStream is = new FileInputStream(sourcePath);OutputStream os = new FileOutputStream(targetPath)) {byte[] buffer = new byte[1024 * 8];int len;while ((len = is.read(buffer)) != -1) {os.write(buffer, 0, len);}System.out.println("文件复制成功!");} catch (Exception e) {e.printStackTrace();}}
}

3.文件字符输入流-适合文本


3.1文件字符输入流构造器和方法

- 以下是使用FileReader(文件字符输入流)读取文本文件的案例代码,展示了不同读取方式的用法(适合读取文本文件,能避免中文乱码):
案例 1:单个字符读取
public class FileReaderDemo1 {public static void main(String[] args) throws Exception {// 1. 创建字符输入流管道,连接源文件(文本文件路径)Reader fr = new FileReader("src/test.txt");// 2. 读取数据:每次读取一个字符int c; // 存储读取到的字符(ASCII码或Unicode码)while ((c = fr.read()) != -1) { // -1表示读取到文件末尾System.out.print((char) c); // 转换为字符并输出}// 3. 关闭流fr.close();}说明:read()方法每次读取一个字符(包括中文,因为字符流会自动处理编码),返回字符的 Unicode 值,转换为char即可正常显示。
}
案例 2:字符数组批量读取
public class FileReaderDemo2 {public static void main(String[] args) {Reader fr = null;try {// 1. 连接源文件fr = new FileReader("src/test.txt");// 2. 定义字符数组作为缓冲区(批量读取,减少IO次数)char[] buffer = new char[1024]; // 每次最多读1024个字符int len; // 记录每次实际读取的字符数// 3. 循环读取while ((len = fr.read(buffer)) != -1) {// 将字符数组的有效部分转换为字符串输出(0到len-1)System.out.print(new String(buffer, 0, len));}} catch (Exception e) {e.printStackTrace();} finally {// 4. 关闭流(确保资源释放)try {if (fr != null) fr.close();} catch (Exception e) {e.printStackTrace();}}}
}
说明:通过字符数组buffer批量读取,len记录实际读取的字符数,
避免读取到数组中残留的旧数据,效率远高于单个字符读取。
案例 3:结合 try-with-resources 自动关闭流(推荐,简化写法)
public class FileReaderDemo3 {public static void main(String[] args) {// try-with-resources:流会自动关闭,无需手动调用close()try (Reader fr = new FileReader("src/test.txt")) {char[] buffer = new char[512];int len;while ((len = fr.read(buffer)) != -1) {System.out.print(new String(buffer, 0, len));}} catch (Exception e) {e.printStackTrace();}}
}


3.2 文件字符输出流
- 文件字符输入流构造器和方法

- 以下是使用FileWriter(文件字符输出流)写入文本文件的案例代码,覆盖不同写入方式和场景(适合处理文本内容,支持中文正常写入):
案例 1:基本写入(覆盖模式)
public class FileWriterDemo1 {public static void main(String[] args) throws Exception {// 1. 创建字符输出流管道(文件不存在会自动创建;存在则覆盖原内容)Writer fw = new FileWriter("output.txt");// 2. 写入数据// 方式1:写单个字符(int参数表示字符的Unicode值)fw.write('A'); // 写入字符'A'fw.write(97); // 写入字符'a'(ASCII码97)fw.write('\n'); // 写入换行符// 方式2:写字符串(最常用,直接写入完整字符串)fw.write("Hello, FileWriter!\n");fw.write("中文写入也没问题~\n");// 方式3:写字符数组char[] chars = {'J', 'a', 'v', 'a', '!', '\n'};fw.write(chars);// 方式4:写字符串的一部分(从索引2开始,写5个字符)fw.write("abcdefghij", 2, 5); // 写入"cdefg"// 3. 关闭流(会自动刷新缓冲区,确保数据写入文件)fw.close();System.out.println("写入完成!");}
}
案例 2:追加写入(不覆盖原内容)
import java.io.FileWriter;
import java.io.Writer;public class FileWriterDemo2 {public static void main(String[] args) {Writer fw = null;try {// 1. 创建输出流(第二个参数为true表示追加模式)fw = new FileWriter("output.txt", true);// 2. 追加写入内容fw.write("\n--- 这是追加的内容 --- \n");fw.write("追加模式下,新内容会加在文件末尾~\n");} catch (Exception e) {e.printStackTrace();} finally {// 3. 关闭流(释放资源)try {if (fw != null) fw.close();} catch (Exception e) {e.printStackTrace();}}}
}
案例 3:使用 try-with-resources 自动关流(推荐)
public class FileWriterDemo3 {public static void main(String[] args) {// try-with-resources:流会自动关闭,无需手动调用close()try (Writer fw = new FileWriter("output.txt", true)) {// 写入字符数组的一部分(从索引1开始,写3个字符)char[] data = {'春', '夏', '秋', '冬'};fw.write(data, 1, 3); // 写入"夏秋"fw.write("\n使用try-with-resources更安全!\n");} catch (Exception e) {e.printStackTrace();}无需手动关闭流,代码更简洁,且能确保资源释放,适合实际开发。}
}

注意

- 关键注意事项,核心是说明字符输出流在写入数据后,必须通过刷新(flush())或关闭(close())操作,才能让数据真正写入文件。
关键内容解释
- 核心规则:字符输出流写出数据后,数据会先存到内存的缓冲区中,不会立即写入文件。必须执行flush()刷新流,或执行close()关闭流,才能将缓冲区的数据强制写入文件,使数据生效。
FileWriter fw = new FileWriter("test.txt");fw.write("Hello");fw.flush(); // 数据立即写入文件,文件中已有“Hello”// 或执行 close()fw.close(); // 关闭时自动刷新,数据也会写入文件
