第八章 文件与输入输出流
第二节 输入输出流的使用
概述
输入输出流(Input/Output Streams)是面向对象程序设计中进行数据交换的基础机制,尤其是在文件处理、网络通信和设备交互中扮演重要角色。本节重点讲解输入输出流的基本概念、工作原理、使用方法及常见技巧。通过系统学习,考生能够掌握Java语言中输入输出流的创建、读写操作、缓冲机制、异常处理及流的关闭规范,进而能够熟练进行文件读写以及数据流转换操作。
学习目标包括:
- 理解输入输出流的基本概念和分类
- 掌握常用输入输出流类的使用方法
- 能够正确处理文件的读写操作
- 熟悉缓冲流的优势及使用方式
- 了解流的异常处理及关闭规范
- 通过典型实例巩固理论知识
核心概念
输入流(InputStream):用于读取数据的字节流,提供从数据源(如文件、内存、网络)读取字节的能力。
输出流(OutputStream):用于写出数据的字节流,负责将数据写入目标(如文件、内存、网络)。
字符流(Reader/Writer):以字符为单位进行读写的流,处理文本数据,支持字符编码转换。
缓冲流(BufferedInputStream/BufferedOutputStream/BufferedReader/BufferedWriter):对基本流进行封装,使用缓冲区提升读写效率。
字节流与字符流区别:字节流以字节为单位操作,适合处理所有类型数据;字符流以字符为单位,适合文本文件处理。
流的链式操作:通过多个流的包装,组合实现复杂功能,如缓冲、数据转换等。
异常处理:IO操作易产生异常,必须通过try-catch-finally或try-with-resources进行安全处理。
流的关闭:使用完流必须关闭释放资源,避免内存泄漏和文件锁定。
原理分析
输入输出流的核心原理基于字节和字符的顺序读取与写入,所有流均围绕“数据从源头流入程序”或“数据从程序流向目标”的方向构建。流是单向的,数据一次只能沿一个方向流动。
字节流直接处理8位字节,适合所有文件类型(文本、图片、音频等)。
字符流在字节流基础上,结合字符编码表(如UTF-8、GBK),将字节解码为字符,方便文本数据处理。字符流继承自Reader和Writer类。
缓冲流的设计原理是减少物理设备的访问次数,通过内存中的缓冲区批量读写数据,提升效率。比如,BufferedInputStream在读取时会先将一大块数据读入缓冲区,后续读取直接从缓冲区返回,减少磁盘访问。
流的链式操作体现面向对象设计的装饰模式,通过组合多个流对象强化功能,如用BufferedReader包装FileReader,实现带缓冲的字符读取。
流的关闭机制确保数据完整写入磁盘,释放系统资源,是流操作的必备步骤。Java 7引入的try-with-resources语法自动管理流资源,简化编码。
详细内容
1. 输入输出流的分类与基本用法
Java中流分为字节流和字符流两大类,分别对应不同的类层级:
- 字节输入流基类:InputStream
- 字节输出流基类:OutputStream
- 字符输入流基类:Reader
- 字符输出流基类:Writer
常用的文件字节流类有:FileInputStream、FileOutputStream;
常用的字符流类有:FileReader、FileWriter。
文件读取示例(字节流):
FileInputStream fis = new FileInputStream("data.txt");
int b;
while ((b = fis.read()) != -1) {
System.out.print((char) b);
}
fis.close();
文件写入示例(字符流):
FileWriter fw = new FileWriter("output.txt");
fw.write("Hello, World!\n");
fw.close();
2. 缓冲流的使用及优势
缓冲流通过内部缓冲区减少物理读写次数,提升性能。常用缓冲流类有:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。
缓冲字符流读取示例:
BufferedReader br = new BufferedReader(new FileReader("data.txt"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
缓冲流常用于大文件读写,特别是需要按行读取文本文件时,readLine()方法极为方便。
3. 流的异常处理与关闭规范
IO操作易发生FileNotFoundException、IOException等异常,必须妥善处理。推荐使用try-with-resources结构,自动关闭流,代码简洁且安全。
示例:
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
若不使用try-with-resources,则需在finally块中关闭流,避免资源泄露。
4. 文件复制实例
通过字节流复制文件是基础操作,涉及输入输出流的配合使用。
示例代码:
try (FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("target.jpg")) {
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}
} catch (IOException e) {
e.printStackTrace();
}
该示例体现缓冲数组的应用,提升复制效率。
5. 字符编码转换的输入输出流
字符流默认使用平台编码,读取或写入非默认编码文本可能出现乱码。解决方法是使用InputStreamReader和OutputStreamWriter指定编码。
示例:
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("data_utf8.txt"), "UTF-8");
BufferedReader br = new BufferedReader(isr)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
实例分析
实例一:文本文件内容统计
背景:统计文本文件中字符数、单词数和行数,是处理文本数据的常见任务。
分析:使用BufferedReader逐行读取文件,行数递增;每行字符数累加;通过空格分割单词数。
代码示例:
public class FileStatistics {
public static void main(String[] args) {
String filePath = "sample.txt";
int lineCount = 0;
int wordCount = 0;
int charCount = 0;
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = br.readLine()) != null) {
lineCount++;
charCount += line.length();
String[] words = line.trim().split("\\s+");
if (words.length == 1 && words[0].isEmpty()) {
continue;
}
wordCount += words.length;
}
System.out.println("Lines: " + lineCount);
System.out.println("Words: " + wordCount);
System.out.println("Characters: " + charCount);
} catch (IOException e) {
e.printStackTrace();
}
}
}
结论:合理使用缓冲字符流和字符串处理方法,实现文本统计功能,体现输入流与字符串操作结合应用。
实例二:图片文件复制与校验
背景:复制图片文件后,需要校验目标文件与源文件是否一致。
分析:使用FileInputStream和FileOutputStream复制文件,复制完后比较两个文件的字节数和MD5摘要。
核心代码片段:
// 复制文件
try (FileInputStream fis = new FileInputStream("source.png");
FileOutputStream fos = new FileOutputStream("copy.png")) {
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
// 校验文件大小
File sourceFile = new File("source.png");
File copyFile = new File("copy.png");
if (sourceFile.length() == copyFile.length()) {
System.out.println("文件大小一致,复制成功。");
} else {
System.out.println("文件大小不一致,复制失败。");
}
结论:通过字节流完成二进制文件复制,配合文件属性校验保证复制准确。
实例三:控制台输入输出实现简单交互
背景:通过标准输入输出流实现用户输入数据并回显。
分析:使用Scanner封装System.in读取输入,使用System.out打印输出,体现流在交互中的角色。
示例代码:
import java.util.Scanner;
public class ConsoleIO {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入您的名字:");
String name = scanner.nextLine();
System.out.println("您好," + name + "!");
scanner.close();
}
}
结论:标准输入输出流方便实现控制台交互,Scanner是输入流的常用封装类。
常见误区
- 误区一:未关闭流导致资源泄漏
- 错误做法:使用完流不调用close(),导致文件被占用或内存泄漏。
- 正确做法:使用try-with-resources或finally块确保关闭流。
- 误区二:字符流处理二进制文件
- 错误做法:用FileReader/FileWriter处理图片、音频等二进制文件,导致数据损坏。
- 正确做法:二进制文件用字节流FileInputStream/FileOutputStream。
- 误区三:忽略缓冲流提升效率
- 错误做法:直接使用FileInputStream/FileOutputStream读取大量数据,效率低。
- 正确做法:用缓冲流包装基本流,提升性能。
- 误区四:忘记处理异常
- 错误做法:IO操作不捕获异常,程序可能崩溃。
- 正确做法:捕获并处理IOException,保证程序健壮。
- 误区五:错误的编码导致乱码
- 错误做法:读取UTF-8文件时未指定编码,使用平台默认编码。
- 正确做法:用InputStreamReader指定正确编码读取文件。
应用场景
- 文件读写与处理:文本编辑器、配置文件读写、日志文件管理等。
- 数据传输:网络通信数据流读取与发送。
- 多媒体文件复制与处理:图片、音视频文件的复制、备份。
- 用户交互:控制台输入输出实现命令行程序交互。
- 数据转换:字符集转换、文件格式转换。
知识拓展
- Java NIO(New IO):Java 1.4引入的非阻塞IO,提供缓冲区和通道,性能更高,适合大文件和网络应用。
- 对象序列化流(ObjectInputStream/ObjectOutputStream):实现对象的持久化存储与传输。
- 数据流(DataInputStream/DataOutputStream):支持Java基本数据类型的读写。
- 文件通道(FileChannel):零拷贝技术,提高文件读写效率。
- 异步IO(Asynchronous IO):Java 7引入,实现非阻塞异步操作。
总结回顾
本节深入讲解了输入输出流的使用,覆盖了字节流与字符流的分类、缓冲流的原理及优势、流的异常处理与关闭规范。通过多个典型实例,考生能够理解输入输出流的实际应用,掌握文件读写、数据复制、编码转换和控制台交互等重要技能。同时,列举了常见误区,帮助避免编码过程中出现的错误。掌握本节内容,将为面向对象程序设计中的文件操作和数据流处理打下坚实基础,是全国计算机等级考试四级的重要知识点。