本文目录导读:

字节流是 Java I/O 的核心概念之一,主要用于处理二进制数据(如图片、音频、视频、压缩文件等),下面通过几个典型案例来详细说明如何使用字节流。
基本概念回顾
字节流的顶层抽象类:
- InputStream:字节输入流
- OutputStream:字节输出流
常用实现类:
FileInputStream/FileOutputStream:文件字节流BufferedInputStream/BufferedOutputStream:缓冲字节流DataInputStream/DataOutputStream:数据字节流ObjectInputStream/ObjectOutputStream:对象字节流
案例一:文件复制(基础操作)
这个案例展示了字节流最基本的读写操作。
import java.io.*;
public class FileCopyExample {
public static void main(String[] args) {
// 源文件和目标文件路径
String sourceFile = "source.jpg";
String destFile = "dest.jpg";
try (FileInputStream fis = new FileInputStream(sourceFile);
FileOutputStream fos = new FileOutputStream(destFile)) {
// 方式1:逐字节读取(效率低,不推荐)
// int byteData;
// while ((byteData = fis.read()) != -1) {
// fos.write(byteData);
// }
// 方式2:使用缓冲区(推荐)
byte[] buffer = new byte[1024]; // 1KB缓冲区
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead); // 写入实际读取的字节数
}
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
关键点:
- 使用
try-with-resources自动关闭资源 - 使用缓冲区提高效率
read(buffer)返回实际读取的字节数
案例二:图片文件加密/解密
演示如何通过修改字节数据实现简单的加密功能。
import java.io.*;
public class ImageEncryptor {
// 简单的 XOR 加密/解密
private static final byte KEY = 0x5A; // 密钥
public static void encryptFile(String inputPath, String outputPath) throws IOException {
try (FileInputStream fis = new FileInputStream(inputPath);
FileOutputStream fos = new FileOutputStream(outputPath)) {
byte[] buffer = new byte[4096]; // 4KB缓冲区
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
// 对每个字节进行 XOR 操作
for (int i = 0; i < bytesRead; i++) {
buffer[i] ^= KEY;
}
fos.write(buffer, 0, bytesRead);
}
}
}
public static void main(String[] args) {
try {
// 加密图片
encryptFile("original.jpg", "encrypted.jpg");
System.out.println("图片加密成功!");
// 解密图片(同样的操作)
encryptFile("encrypted.jpg", "decrypted.jpg");
System.out.println("图片解密成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
案例三:读取二进制文件并解析
演示如何读取二进制文件格式(如 BMP 图片头信息)。
import java.io.*;
public class BMPHeaderReader {
public static void readBMPHeader(String filePath) throws IOException {
try (FileInputStream fis = new FileInputStream(filePath);
BufferedInputStream bis = new BufferedInputStream(fis)) {
// BMP文件头(共14字节)
byte[] header = new byte[14];
bis.read(header);
// 验证BMP文件标识
if (header[0] != 'B' || header[1] != 'M') {
System.out.println("不是有效的BMP文件");
return;
}
// 读取文件大小(4字节,小端序)
int fileSize = readLittleEndianInt(header, 2);
System.out.println("文件大小: " + fileSize + " 字节");
// 读取位图数据偏移量(4字节)
int dataOffset = readLittleEndianInt(header, 10);
System.out.println("数据偏移量: " + dataOffset + " 字节");
// 读取DIB头(通常40字节)
byte[] dibHeader = new byte[40];
bis.read(dibHeader);
// 读取宽度和高度
int width = readLittleEndianInt(dibHeader, 4);
int height = readLittleEndianInt(dibHeader, 8);
System.out.println("图像尺寸: " + width + " x " + height);
// 读取位深度(2字节)
int bitDepth = readLittleEndianShort(dibHeader, 14);
System.out.println("位深度: " + bitDepth + " bit");
}
}
// 读取小端序的4字节整数
private static int readLittleEndianInt(byte[] data, int offset) {
return (data[offset] & 0xFF) |
((data[offset + 1] & 0xFF) << 8) |
((data[offset + 2] & 0xFF) << 16) |
((data[offset + 3] & 0xFF) << 24);
}
// 读取小端序的2字节整数
private static short readLittleEndianShort(byte[] data, int offset) {
return (short)((data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8));
}
public static void main(String[] args) {
try {
readBMPHeader("image.bmp");
} catch (IOException e) {
e.printStackTrace();
}
}
}
案例四:带缓冲的复制(高性能)
使用 BufferedInputStream 和 BufferedOutputStream 提升性能。
import java.io.*;
public class BufferedCopyExample {
public static void bufferedCopy(String source, String dest) throws IOException {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
// BufferedOutputStream 需要手动 flush
bos.flush();
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
try {
bufferedCopy("large_file.mp4", "copy_large_file.mp4");
long endTime = System.currentTimeMillis();
System.out.println("拷贝完成,耗时: " + (endTime - startTime) + "ms");
} catch (IOException e) {
e.printStackTrace();
}
}
}
性能对比与最佳实践
各种方式的性能对比
public class PerformanceComparison {
public static void main(String[] args) throws IOException {
String source = "test.bin";
long startTime, endTime;
// 1. 无缓冲逐字节(最慢)
startTime = System.currentTimeMillis();
copyNoBuffer(source, "copy1.bin");
endTime = System.currentTimeMillis();
System.out.println("无缓冲逐字节: " + (endTime - startTime) + "ms");
// 2. 有缓冲区(较快)
startTime = System.currentTimeMillis();
copyWithBuffer(source, "copy2.bin");
endTime = System.currentTimeMillis();
System.out.println("有缓冲区(1024): " + (endTime - startTime) + "ms");
// 3. 缓冲流(最快)
startTime = System.currentTimeMillis();
copyWithBufferedStream(source, "copy3.bin");
endTime = System.currentTimeMillis();
System.out.println("缓冲流: " + (endTime - startTime) + "ms");
}
private static void copyNoBuffer(String src, String dest) throws IOException {
try (FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)) {
int byteData;
while ((byteData = fis.read()) != -1) {
fos.write(byteData);
}
}
}
private static void copyWithBuffer(String src, String dest) throws IOException {
try (FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
}
private static void copyWithBufferedStream(String src, String dest) throws IOException {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
}
}
}
常见错误处理
public class ErrorHandlingExample {
public void safeFileCopy(String source, String dest) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 检查源文件是否存在
File sourceFile = new File(source);
if (!sourceFile.exists()) {
System.err.println("源文件不存在: " + source);
return;
}
// 检查目标目录是否存在
File destFile = new File(dest);
File parentDir = destFile.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs(); // 创建目录
}
fis = new FileInputStream(sourceFile);
fos = new FileOutputStream(destFile);
byte[] buffer = new byte[4096];
int bytesRead;
long totalBytes = 0;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
totalBytes += bytesRead;
}
System.out.println("成功复制 " + totalBytes + " 字节");
} catch (FileNotFoundException e) {
System.err.println("文件未找到: " + e.getMessage());
} catch (IOException e) {
System.err.println("IO错误: " + e.getMessage());
} finally {
// 确保资源关闭
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
总结与最佳实践
- 使用缓冲区:永远不要逐字节读取,使用
byte[]缓冲区(建议 4KB-8KB) - 使用缓冲流:
BufferedInputStream/BufferedOutputStream能显著提升性能 - 及时关闭资源:使用
try-with-resources自动关闭,或确保在 finally 中关闭 - 选择合适的缓冲区大小:8192 字节(8KB)是一个好的选择
- 注意编码问题:字节流处理二进制数据,字符流处理文本数据,不要混淆
字节流是 Java I/O 的基础,掌握好这些案例和技巧,可以处理绝大多数文件操作场景。