目录
[3.1 构造方法](#3.1 构造方法)
[3.2 核心方法:read()](#3.2 核心方法:read())
[1. read() - 读取单个字节](#1. read() - 读取单个字节)
[2. read(byte\[\] b) - 读取到字节数组](#2. read(byte[] b) - 读取到字节数组)
[3. read(byte\[\] b, int off, int len) - 带偏移量的读取](#3. read(byte[] b, int off, int len) - 带偏移量的读取)
[4.1 基础读取示例](#4.1 基础读取示例)
[4.2 中文读取问题分析](#4.2 中文读取问题分析)
[4.3 优化方案:使用字节数组读取](#4.3 优化方案:使用字节数组读取)
[5.1 构造方法](#5.1 构造方法)
[5.2 核心方法:write()](#5.2 核心方法:write())
[5.3 实战示例:写入字符串到文件](#5.3 实战示例:写入字符串到文件)
[7.1 BufferedInputStream缓冲字节输入流](#7.1 BufferedInputStream缓冲字节输入流)
[7.2 ObjectInputStream对象字节输入流](#7.2 ObjectInputStream对象字节输入流)
一、InputStream:字节输入流概述
InputStream 是 Java IO 体系中所有字节输入流类的抽象父类,它定义了读取字节数据的基本操作。作为抽象类,InputStream 本身不能被实例化,但它的各种子类为不同的数据源(如文件、网络、内存等)提供了具体的实现。
二、InputStream常用子类
InputStream 的主要子类包括:
- FileInputStream:文件输入流,用于从文件中读取字节数据
- ByteArrayInputStream:字节数组输入流,从内存中的字节数组读取数据
- BufferedInputStream:缓冲字节输入流,提供缓冲功能以提高读取效率
- ObjectInputStream:对象字节输入流,用于反序列化对象
- DataInputStream:数据输入流,用于读取基本数据类型
三、FileInputStream文件输入流详解
3.1 构造方法
FileInputStream 提供了三种主要的构造方法:
java
// 1. 通过File对象构造
File file = new File("test.txt");
FileInputStream fis1 = new FileInputStream(file);
// 2. 通过文件描述符构造
FileDescriptor fd = ...;
FileInputStream fis2 = new FileInputStream(fd);
// 3. 通过文件路径字符串构造
FileInputStream fis3 = new FileInputStream("test.txt");
3.2 核心方法:read()
FileInputStream 提供了三个重载的 read() 方法:
1. read() - 读取单个字节
java
// 从输入流中读取一个字节,返回值为int类型(0-255)
// 当读取到文件末尾时返回-1
int byteData = fis.read();
2. read(byte\[\] b) - 读取到字节数组
java
// 从输入流中读取最多b.length个字节到字节数组b中
// 返回实际读取的字节数,文件末尾返回-1
byte[] buffer = new byte[1024];
int bytesRead = fis.read(buffer);
3. read(byte\[\] b, int off, int len) - 带偏移量的读取
java
// 从输入流中读取最多len个字节到字节数组b中
// off表示数组b中的起始偏移量
byte[] buffer = new byte[1024];
int bytesRead = fis.read(buffer, 0, 1024);
四、FileInputStream实战:读取文本文件
4.1 基础读取示例
使用 FileInputStream 读取 test.txt 文件内容并打印到控制台:
java
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamDemo {
public static void main(String[] args) {
FileInputStream fis = null;
try {
// 创建文件输入流
fis = new FileInputStream("test.txt");
int byteData;
// 循环读取直到文件末尾
while ((byteData = fis.read()) != -1) {
System.out.print((char) byteData);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 必须关闭流释放资源
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4.2 中文读取问题分析
**重要提醒:**使用 read() 方法读取中文文件会出现乱码!
原因分析:
- 中文字符在 UTF-8 编码中通常占用 3 个字节
- read() 方法每次只能读取 1 个字节
- 将单个字节强制转换为 char 会导致编码错误
示例:读取 "你好" 这两个中文字符:
java
// 错误示例 - 会出现乱码
while ((byteData = fis.read()) != -1) {
System.out.print((char) byteData); // 输出乱码
}
4.3 优化方案:使用字节数组读取
为了提高读取效率和正确处理中文,推荐使用 read(byte\[\] b) 方法:
java
import java.io.FileInputStream;
import java.io.IOException;
public class EfficientFileReader {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
byte[] buffer = new byte[1024]; // 1KB缓冲区
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
// 将字节数组转换为字符串(指定编码)
String content = new String(buffer, 0, bytesRead, "UTF-8");
System.out.print(content);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
**注意:**如果一次读取的数据量小于数组长度,之前读取的值不会被清空,因此需要使用 bytesRead 参数来限制转换的范围。
五、FileOutputStream文件输出流
5.1 构造方法
FileOutputStream 的主要构造方法:
java
// 1. 通过文件路径创建(覆盖模式)
FileOutputStream fos1 = new FileOutputStream("output.txt");
// 2. 通过文件路径创建(追加模式)
FileOutputStream fos2 = new FileOutputStream("output.txt", true);
// 3. 通过File对象创建
File file = new File("output.txt");
FileOutputStream fos3 = new FileOutputStream(file);
// 4. 通过File对象创建(追加模式)
FileOutputStream fos4 = new FileOutputStream(file, true);
**参数说明:**第二个 boolean 参数为 true 表示追加模式,为 false 或不写表示覆盖模式。
5.2 核心方法:write()
FileOutputStream 提供了三个重载的 write() 方法:
java
// 1. 写入单个字节
fos.write(65); // 写入字符'A'
// 2. 写入字节数组
byte[] data = "Hello".getBytes();
fos.write(data);
// 3. 写入字节数组的指定部分
byte[] data2 = "Hello World".getBytes();
fos.write(data2, 6, 5); // 写入"World"
5.3 实战示例:写入字符串到文件
java
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
// 创建文件输出流(如果文件不存在会自动创建)
fos = new FileOutputStream("test.txt");
// 要写入的字符串
String content = "Hello World";
// 将字符串转换为字节数组(可指定编码)
byte[] bytes = content.getBytes("UTF-8");
// 写入文件
fos.write(bytes);
System.out.println("文件写入成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
六、综合实战:图片文件拷贝
使用 FileInputStream 和 FileOutputStream 实现图片文件拷贝功能:
java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ImageCopy {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 创建输入流读取源图片
fis = new FileInputStream("source.png");
// 创建输出流写入目标文件
fos = new FileOutputStream("copy.png");
// 使用缓冲区提高拷贝效率
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
// 循环读取并写入
while ((bytesRead = fis.read(buffer)) != -1) {
// 重要:必须指定长度,否则可能写入错误数据
fos.write(buffer, 0, bytesRead);
}
System.out.println("图片拷贝完成!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭输入流
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 关闭输出流
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
**重要提醒:**在 write() 方法中必须指定长度参数(bytesRead),如果错误地写成 write(buffer),可能会写入多余的旧数据,导致文件大小不正确。虽然某些图片查看器可能仍然能打开,但文件内容已经损坏。
七、其他重要字节流
7.1 BufferedInputStream缓冲字节输入流
BufferedInputStream 为输入流提供缓冲功能,可以显著提高读取效率:
java
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class BufferedStreamDemo {
public static void main(String[] args) {
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("largefile.dat"))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
// 处理数据
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
7.2 ObjectInputStream对象字节输入流
ObjectInputStream 用于反序列化对象,通常与 ObjectOutputStream 配合使用:
java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectStreamDemo {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("object.dat"))) {
// 读取对象
Object obj = ois.readObject();
System.out.println("读取的对象: " + obj);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
八、总结与最佳实践
**1. 资源管理:**必须使用 try-with-resources 或 finally 块确保流被关闭
**2. 编码处理:**处理文本文件时明确指定字符编码(如 UTF-8)
**3. 缓冲区使用:**使用适当大小的缓冲区提高IO效率
**4. 异常处理:**正确处理 IOException,避免资源泄漏
**5. 文件操作:**FileOutputStream 会自动创建不存在的文件,但不会创建目录
通过本文的学习,你应该已经掌握了 Java 字节输入输出流的基本原理和实战应用。在实际开发中,建议优先使用 Java NIO 的 Files 和 Paths 类进行文件操作,它们提供了更简洁、更安全的API。