第58章

Java字节流

深入学习InputStream和OutputStream的使用方法,掌握Java字节流的核心概念和实际应用

学习目标

字节流概述

字节流是Java I/O系统的基础,用于处理二进制数据。所有的字节流都继承自InputStreamOutputStream抽象类。

字节流的特点:
  • 处理二进制数据:适合处理图片、音频、视频等二进制文件
  • 以字节为单位:每次读写一个或多个字节
  • 通用性强:可以处理任何类型的文件
  • 效率较高:直接操作字节,没有编码转换开销

主要字节流类型

基础字节流

  • FileInputStream / FileOutputStream
  • 直接操作文件
  • 适合小文件操作
  • 需要手动关闭资源

缓冲字节流

  • BufferedInputStream / BufferedOutputStream
  • 内置缓冲区,减少系统调用
  • 显著提高I/O性能
  • 适合大文件操作

数据字节流

  • DataInputStream / DataOutputStream
  • 可以直接读写Java基本数据类型
  • 保持数据类型的完整性
  • 适合结构化数据存储

字节数组流

  • ByteArrayInputStream / ByteArrayOutputStream
  • 在内存中操作,不涉及磁盘I/O
  • 速度快,适合临时数据处理
  • 支持动态扩容

基础字节流示例

FileInputStream 和 FileOutputStream

文件读写基础示例
import java.io.*;

public class ByteStreamBasics {
    
    // 演示FileInputStream读取文件
    public static void demonstrateFileInputStream(String fileName) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(fileName);
            
            // 逐字节读取
            int byteData;
            while ((byteData = fis.read()) != -1) {
                System.out.print((char)byteData);
            }
            
        } catch (IOException e) {
            System.err.println("读取文件时发生错误: " + e.getMessage());
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    System.err.println("关闭流时发生错误: " + e.getMessage());
                }
            }
        }
    }
    
    // 演示FileOutputStream写入文件
    public static void demonstrateFileOutputStream(String fileName, String content) {
        try (FileOutputStream fos = new FileOutputStream(fileName)) {
            // 将字符串转换为字节数组并写入
            byte[] bytes = content.getBytes();
            fos.write(bytes);
            
            System.out.println("成功写入文件: " + fileName);
            
        } catch (IOException e) {
            System.err.println("写入文件时发生错误: " + e.getMessage());
        }
    }
}
💻 查看完整代码 - 在线IDE体验

缓冲字节流

缓冲流通过内置缓冲区来减少系统调用次数,显著提高I/O性能。

缓冲流性能比较示例
// 使用普通流复制文件
private static void copyFileWithNormalStream(String sourceFile, String targetFile) {
    try (FileInputStream fis = new FileInputStream(sourceFile);
         FileOutputStream fos = new FileOutputStream(targetFile)) {
        
        int byteData;
        while ((byteData = fis.read()) != -1) {
            fos.write(byteData);
        }
        
    } catch (IOException e) {
        System.err.println("普通流复制文件失败: " + e.getMessage());
    }
}

// 使用缓冲流复制文件
private static void copyFileWithBufferedStream(String sourceFile, String targetFile) {
    try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetFile))) {
        
        int byteData;
        while ((byteData = bis.read()) != -1) {
            bos.write(byteData);
        }
        
    } catch (IOException e) {
        System.err.println("缓冲流复制文件失败: " + e.getMessage());
    }
}
性能提升:对于大文件操作,缓冲流通常比普通流快3-10倍,因为它减少了系统调用的次数。

数据字节流

数据流可以直接读写Java基本数据类型,保持数据的完整性和类型信息。

数据流读写示例
// 写入不同类型的数据
public static void demonstrateDataOutput(String fileName) {
    try (DataOutputStream dos = new DataOutputStream(
            new BufferedOutputStream(new FileOutputStream(fileName)))) {
        
        // 写入不同类型的数据
        dos.writeBoolean(true);
        dos.writeByte(127);
        dos.writeShort(32767);
        dos.writeInt(2147483647);
        dos.writeLong(9223372036854775807L);
        dos.writeFloat(3.14159f);
        dos.writeDouble(2.718281828459045);
        dos.writeUTF("Hello, DataStream!");
        
        System.out.println("数据写入完成");
        
    } catch (IOException e) {
        System.err.println("写入数据时发生错误: " + e.getMessage());
    }
}

// 按写入顺序读取数据
public static void demonstrateDataInput(String fileName) {
    try (DataInputStream dis = new DataInputStream(
            new BufferedInputStream(new FileInputStream(fileName)))) {
        
        boolean boolValue = dis.readBoolean();
        byte byteValue = dis.readByte();
        short shortValue = dis.readShort();
        int intValue = dis.readInt();
        long longValue = dis.readLong();
        float floatValue = dis.readFloat();
        double doubleValue = dis.readDouble();
        String stringValue = dis.readUTF();
        
        // 输出读取的数据
        System.out.println("boolean: " + boolValue);
        System.out.println("byte: " + byteValue);
        System.out.println("short: " + shortValue);
        System.out.println("int: " + intValue);
        System.out.println("long: " + longValue);
        System.out.println("float: " + floatValue);
        System.out.println("double: " + doubleValue);
        System.out.println("String: " + stringValue);
        
    } catch (IOException e) {
        System.err.println("读取数据时发生错误: " + e.getMessage());
    }
}

字节数组流

字节数组流在内存中操作,不涉及磁盘I/O,适合临时数据处理和数据转换。

字节数组流示例
// 演示ByteArrayOutputStream的使用
public static void demonstrateByteArrayOutput() {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
    try {
        // 写入字符串数据
        String text1 = "Hello, ";
        String text2 = "ByteArrayStream!";
        
        baos.write(text1.getBytes());
        baos.write(text2.getBytes());
        
        // 获取字节数组
        byte[] data = baos.toByteArray();
        
        System.out.println("写入的数据: " + new String(data));
        System.out.println("字节数组长度: " + data.length);
        System.out.println("流的大小: " + baos.size());
        
        // 重置流
        baos.reset();
        System.out.println("重置后流的大小: " + baos.size());
        
    } catch (IOException e) {
        System.err.println("操作ByteArrayOutputStream时发生错误: " + e.getMessage());
    } finally {
        try {
            baos.close();
        } catch (IOException e) {
            System.err.println("关闭流时发生错误: " + e.getMessage());
        }
    }
}

// 演示ByteArrayInputStream的使用
public static void demonstrateByteArrayInput() {
    String originalText = "Java字节数组流示例";
    byte[] data = originalText.getBytes();
    
    ByteArrayInputStream bais = new ByteArrayInputStream(data);
    
    try {
        System.out.println("原始数据: " + originalText);
        System.out.println("可用字节数: " + bais.available());
        
        // 批量读取
        byte[] buffer = new byte[1024];
        int bytesRead = bais.read(buffer);
        if (bytesRead != -1) {
            String readText = new String(buffer, 0, bytesRead);
            System.out.println("读取的内容: " + readText);
        }
        
    } catch (IOException e) {
        System.err.println("操作ByteArrayInputStream时发生错误: " + e.getMessage());
    } finally {
        try {
            bais.close();
        } catch (IOException e) {
            System.err.println("关闭流时发生错误: " + e.getMessage());
        }
    }
}

字节流类型比较

流类型 适用场景 性能特点 使用建议
FileInputStream/FileOutputStream 小文件读写、简单文件操作 直接操作,无缓冲 适合小文件或一次性操作
BufferedInputStream/BufferedOutputStream 大文件读写、频繁I/O操作 高性能,减少系统调用 推荐用于大文件操作
DataInputStream/DataOutputStream 结构化数据存储、基本类型读写 类型安全,数据完整 适合配置文件、数据交换
ByteArrayInputStream/ByteArrayOutputStream 内存数据处理、临时存储 内存操作,速度最快 适合数据转换、临时缓存

字节流最佳实践

编程建议

推荐做法

  • 使用try-with-resources
    自动管理资源,确保流被正确关闭
  • 使用缓冲流提高性能
    对于大文件操作,使用BufferedInputStream/BufferedOutputStream
  • 批量读写数据
    使用字节数组而不是逐字节操作
  • 选择合适的缓冲区大小
    根据文件大小和内存情况选择合适的缓冲区

避免的做法

  • 忘记关闭流
    可能导致资源泄露和文件锁定
  • 逐字节处理大文件
    效率低下,应使用缓冲区
  • 不使用缓冲区
    对于大文件,直接使用FileInputStream效率较低
  • 忽略异常处理
    I/O操作容易出错,必须妥善处理异常

性能优化建议

缓冲区大小选择

  • 小文件:512字节 - 4KB
  • 大文件:8KB - 64KB
  • 网络传输:1KB - 8KB

注意事项

  • 缓冲区过大会占用更多内存
  • 缓冲区过小会增加系统调用次数
  • 需要在内存使用和性能之间找到平衡

章节总结