Java InputStream和OutputStream实现类完全指南

Java InputStream和OutputStream实现类完全指南

#

在这里插入图片描述


文章目录

在这里插入图片描述

引言:Java I/O体系概述

Java的I/O(输入/输出)是通过流(Stream)实现的,流是一组有序的数据序列。根据数据的流向,流可以分为输入流(InputStream)和输出流(OutputStream)。字节流以字节(8位二进制)为单位处理数据,可以操作任何类型的文件,包括文本、图片、音频、视频等二进制文件。

InputStreamOutputStream是Java字节流的两个抽象基类,位于java.io包中。它们定义了字节流读写的基本方法,拥有众多的实现类,每个实现类都针对特定的数据源或处理需求进行了优化。本文将详细介绍这些实现类的特点、使用方法和最佳实践。

第一部分:文件操作流

1.1 FileInputStream和FileOutputStream

1.1.1 基本概念

FileInputStreamFileOutputStream是最基础的字节流实现类,用于从文件中读取字节数据或将字节数据写入文件。它们是Java程序与文件系统交互的最直接方式。

1.1.2 构造方法

FileInputStream构造方法

// 通过文件路径创建FileInputStream fis =newFileInputStream(String name);// 通过File对象创建FileInputStream fis =newFileInputStream(File file);// 通过FileDescriptor创建FileInputStream fis =newFileInputStream(FileDescriptor fdObj);

FileOutputStream构造方法

// 通过文件路径创建(覆盖模式)FileOutputStream fos =newFileOutputStream(String name);// 通过文件路径创建(可指定追加模式)FileOutputStream fos =newFileOutputStream(String name,boolean append);// 通过File对象创建(覆盖模式)FileOutputStream fos =newFileOutputStream(File file);// 通过File对象创建(可指定追加模式)FileOutputStream fos =newFileOutputStream(File file,boolean append);
1.1.3 核心方法

FileInputStream主要方法

  • int read(): 读取一个字节,返回0-255的字节值,到达文件末尾返回-1
  • int read(byte[] b): 读取最多b.length字节到数组中,返回实际读取的字节数
  • int read(byte[] b, int off, int len): 读取最多len字节到数组的指定位置
  • long skip(long n): 跳过并丢弃n个字节
  • int available(): 返回可读取的字节数估计值
  • void close(): 关闭流并释放相关资源

FileOutputStream主要方法

  • void write(int b): 写入一个字节
  • void write(byte[] b): 写入整个字节数组
  • void write(byte[] b, int off, int len): 写入字节数组的一部分
  • void flush(): 刷新缓冲区(对于FileOutputStream,write方法直接写入文件,flush通常不做任何操作)
  • void close(): 关闭流并释放资源
1.1.4 使用示例

基本文件复制示例

publicclassFileCopyExample{publicstaticvoidmain(String[] args){// 使用try-with-resources自动关闭资源(JDK 7+)try(FileInputStream fis =newFileInputStream("source.jpg");FileOutputStream fos =newFileOutputStream("dest.jpg")){byte[] buffer =newbyte[8192];// 8KB缓冲区int bytesRead;while((bytesRead = fis.read(buffer))!=-1){ fos.write(buffer,0, bytesRead);}System.out.println("文件复制完成");}catch(IOException e){ e.printStackTrace();}}}

追加模式写入示例

publicclassAppendToFileExample{publicstaticvoidmain(String[] args){String logEntry ="["+newDate()+"] 用户登录系统\n";try(FileOutputStream fos =newFileOutputStream("app.log",true)){ fos.write(logEntry.getBytes(StandardCharsets.UTF_8));System.out.println("日志写入成功");}catch(IOException e){ e.printStackTrace();}}}
1.1.5 性能考虑
  • 单字节读写(read()write(int))效率极低,每次调用都会触发底层系统调用
  • 建议使用缓冲区(字节数组)批量读写,缓冲区大小通常设为4KB-8KB
  • 对于更高性能需求,可以配合缓冲流使用

1.2 FileInputStream的内部工作原理

FileInputStream实际通过JNI(Java Native Interface)调用操作系统底层的文件读取API。在Windows上是ReadFile函数,在Linux/Unix上是read系统调用。因此,频繁的单字节读写会频繁陷入内核态,造成性能瓶颈。


第二部分:缓冲流

2.1 BufferedInputStream和BufferedOutputStream

2.1.1 基本概念

BufferedInputStreamBufferedOutputStream装饰器模式的典型应用,它们为现有的输入输出流添加缓冲功能,通过减少实际的I/O操作次数来显著提升性能。

2.1.2 工作原理

BufferedInputStream内部维护一个字节数组作为缓冲区。当调用read()方法时,它会尽可能多地读取数据填充缓冲区,后续的读取操作直接从缓冲区返回数据,只有当缓冲区数据耗尽时才再次从底层输入流读取。

BufferedOutputStream同样维护一个字节数组作为缓冲区。写入的数据首先存入缓冲区,当缓冲区满或调用flush()方法时,才将缓冲区数据一次性写入底层输出流。

2.1.3 构造方法
// 使用默认缓冲区大小(8192字节)BufferedInputStream bis =newBufferedInputStream(InputStream in);BufferedOutputStream bos =newBufferedOutputStream(OutputStream out);// 指定缓冲区大小BufferedInputStream bis =newBufferedInputStream(InputStream in,int size);BufferedOutputStream bos =newBufferedOutputStream(OutputStream out,int size);
2.1.4 性能对比示例
publicclassBufferedStreamPerformance{publicstaticvoidmain(String[] args){String fileName ="test.dat";// 测试无缓冲写入long start =System.nanoTime();try(FileOutputStream fos =newFileOutputStream(fileName)){for(int i =0; i <1000000; i++){ fos.write(i);// 单字节写入,每次都会触发系统调用}}catch(IOException e){ e.printStackTrace();}long end =System.nanoTime();System.out.println("无缓冲写入时间: "+(end - start)/1_000_000+" ms");// 测试缓冲写入 start =System.nanoTime();try(BufferedOutputStream bos =newBufferedOutputStream(newFileOutputStream(fileName))){for(int i =0; i <1000000; i++){ bos.write(i);// 写入到缓冲区,满8K时才真正写入}}catch(IOException e){ e.printStackTrace();} end =System.nanoTime();System.out.println("缓冲写入时间: "+(end - start)/1_000_000+" ms");// 清理newFile(fileName).delete();}}

运行结果通常显示缓冲流比无缓冲流快几十甚至上百倍。

2.1.5 重要注意事项

关于flush()方法
BufferedOutputStreamflush()方法强制将缓冲区中的数据写入底层输出流。在关闭流之前,close()方法会自动调用flush()。但在以下情况需要手动调用:

  • 需要确保数据立即写入(如日志记录)
  • 写入完成后还要继续使用底层流
  • 长时间写入且希望避免数据滞留缓冲区

关于mark/reset功能
BufferedInputStream支持mark()reset()方法,允许读取位置回退。默认情况下mark有效,但如果在标记后读取的字节数超过缓冲区大小,标记可能会失效。可以通过构造函数指定更大的缓冲区来避免这个问题。

publicclassMarkResetExample{publicstaticvoidmain(String[] args)throwsIOException{byte[] data ="Hello World".getBytes();try(BufferedInputStream bis =newBufferedInputStream(newByteArrayInputStream(data))){// 标记当前位置(起始位置) bis.mark(10);// 参数表示标记有效期内最多可以读取的字节数// 读取前5个字节byte[] buffer =newbyte[5]; bis.read(buffer);System.out.println("第一次读取: "+newString(buffer));// 重置到标记位置 bis.reset();// 重新读取 bis.read(buffer);System.out.println("重置后读取: "+newString(buffer));}}}

2.2 缓冲流的缓冲区大小选择

默认的8192字节(8KB)是经过实践检验的较优值,与文件系统的块大小和CPU缓存行大小相匹配。对于大文件顺序读写,可以适当增大缓冲区(如64KB或256KB)来进一步提升性能。但过大的缓冲区可能导致内存浪费和缓存利用率下降。


第三部分:内存操作流

3.1 ByteArrayInputStream和ByteArrayOutputStream

3.1.1 基本概念

ByteArrayInputStreamByteArrayOutputStream是操作内存中字节数组的流实现类。它们不涉及磁盘或网络I/O,所有操作都在内存中进行,因此速度极快,适合临时数据处理。

3.1.2 特点
  • 无需关闭:这两个流的close()方法是空实现,调用没有实际效果
  • 内存操作:数据存储在JVM堆内存中
  • 线程不安全:多个线程同时访问需要外部同步
  • 自动扩容ByteArrayOutputStream在写入数据时会自动扩容
3.1.3 ByteArrayInputStream详解

构造方法

// 使用整个字节数组作为数据源ByteArrayInputStream bais =newByteArrayInputStream(byte[] buf);// 使用字节数组的一部分作为数据源ByteArrayInputStream bais =newByteArrayInputStream(byte[] buf,int offset,int length);

使用示例

publicclassByteArrayInputStreamExample{publicstaticvoidmain(String[] args){byte[] data ="Java I/O 内存操作流示例".getBytes(StandardCharsets.UTF_8);try(ByteArrayInputStream bais =newByteArrayInputStream(data)){byte[] buffer =newbyte[1024];int bytesRead = bais.read(buffer);String result =newString(buffer,0, bytesRead,StandardCharsets.UTF_8);System.out.println("读取的内容: "+ result);// mark/reset支持(基于数组,完全可用) bais.reset();// 重置到流的开头System.out.println("重置后可用字节数: "+ bais.available());// 跳过前5个字节 bais.skip(5);byte[] remaining = bais.readAllBytes();System.out.println("跳过5字节后: "+newString(remaining,StandardCharsets.UTF_8));}catch(IOException e){ e.printStackTrace();}}}
3.1.4 ByteArrayOutputStream详解

构造方法

// 默认缓冲区大小32字节ByteArrayOutputStream baos =newByteArrayOutputStream();// 指定初始缓冲区大小ByteArrayOutputStream baos =newByteArrayOutputStream(int size);

核心方法

// 写入数据voidwrite(int b)voidwrite(byte[] b,int off,int len)// 获取数据byte[]toByteArray()// 返回当前写入数据的副本StringtoString()// 使用平台默认编码转换为字符串StringtoString(String charsetName)// 使用指定编码转换为字符串// 写入到另一个输出流voidwriteTo(OutputStream out)// 重置(清空缓冲区)voidreset()// 当前缓冲区大小intsize()

综合使用示例

publicclassByteArrayOutputStreamExample{publicstaticvoidmain(String[] args){// 场景1:合并多个数据源try(ByteArrayOutputStream baos =newByteArrayOutputStream()){// 写入不同类型的数据 baos.write("用户名: ".getBytes(StandardCharsets.UTF_8)); baos.write("张三\n".getBytes(StandardCharsets.UTF_8)); baos.write("年龄: ".getBytes(StandardCharsets.UTF_8)); baos.write(String.valueOf(25).getBytes(StandardCharsets.UTF_8));// 获取合并后的数据byte[] combinedData = baos.toByteArray();System.out.println("合并结果:\n"+newString(combinedData,StandardCharsets.UTF_8));// 场景2:写入到文件try(FileOutputStream fos =newFileOutputStream("user.txt")){ baos.writeTo(fos);}// 场景3:重用同一个流 baos.reset(); baos.write("新的数据".getBytes(StandardCharsets.UTF_8));System.out.println("重置后: "+ baos.toString(StandardCharsets.UTF_8.name()));}catch(IOException e){ e.printStackTrace();}// 场景4:作为临时缓冲区处理网络数据byte[] networkData =simulateNetworkReceive();ByteArrayInputStream bais =newByteArrayInputStream(networkData);processData(bais);}privatestaticvoidprocessData(InputStream input){// 处理输入流数据// 这里可以是解析协议、解压缩等操作}privatestaticbyte[]simulateNetworkReceive(){return"模拟的网络数据包".getBytes(StandardCharsets.UTF_8);}}

3.3 其他内存操作流

除了字节数组流,Java还提供了字符数组流和字符串流:

  • CharArrayReader / CharArrayWriter:操作字符数组的字符流
  • StringReader / StringWriter:操作字符串的字符流

这些流在处理文本数据时更加方便,但本质上是为字符流设计的。


第四部分:对象序列化流

4.1 ObjectInputStream和ObjectOutputStream

4.1.1 基本概念

ObjectInputStreamObjectOutputStream是用于对象序列化的高级流,可以将Java对象转换为字节序列(序列化),或者将字节序列恢复为Java对象(反序列化)。

4.1.2 序列化要求
  • 对象类必须实现java.io.Serializable接口(标记接口,无方法需要实现)
  • 所有非静态、非瞬态(transient)字段都会被序列化
  • 如果父类没有实现Serializable,父类必须有默认构造函数
  • 序列化版本ID:private static final long serialVersionUID,用于版本控制
4.1.3 构造方法
// 需要包装另一个输出流ObjectOutputStream oos =newObjectOutputStream(OutputStream out);// 需要包装另一个输入流ObjectInputStream ois =newObjectInputStream(InputStream in);
4.1.4 核心方法

ObjectOutputStream

// 写入对象voidwriteObject(Object obj)// 写入基本类型(实现了DataOutput接口)voidwriteInt(int v)voidwriteBoolean(boolean v)voidwriteUTF(String str)// 写入字符串(Modified UTF-8格式)// 刷新(确保所有数据写入底层流)voidflush()// 重置(清除已缓存的对象引用)voidreset()

ObjectInputStream

// 读取对象ObjectreadObject()// 读取基本类型intreadInt()booleanreadBoolean()StringreadUTF()
4.1.5 使用示例

基本序列化示例

importjava.io.*;importjava.time.LocalDateTime;// 可序列化的用户类classUserimplementsSerializable{privatestaticfinallong serialVersionUID =1L;privateString username;privateString password;// 敏感信息privatetransientString token;// 瞬态字段不序列化privateLocalDateTime loginTime;publicUser(String username,String password){this.username = username;this.password = password;this.loginTime =LocalDateTime.now();}@OverridepublicStringtoString(){return"User{username='"+ username +"',+ password +"',+ token +"', loginTime="+ loginTime +'}';}// getters and setterspublicvoidsetToken(String token){this.token = token;}}publicclassSerializationExample{publicstaticvoidmain(String[] args){String fileName ="user.ser";// 序列化try(ObjectOutputStream oos =newObjectOutputStream(newFileOutputStream(fileName))){User user =newUser("张三","password123"); user.setToken("auth-token-xyz"); oos.writeObject(user);System.out.println("对象序列化完成: "+ user);}catch(IOException e){ e.printStackTrace();}// 反序列化try(ObjectInputStream ois =newObjectInputStream(newFileInputStream(fileName))){User deserializedUser =(User) ois.readObject();System.out.println("对象反序列化完成: "+ deserializedUser);// token字段为null,因为它被标记为transient}catch(IOException|ClassNotFoundException e){ e.printStackTrace();}}}
4.1.6 自定义序列化

类可以定义以下方法来自定义序列化行为:

privatevoidwriteObject(ObjectOutputStream oos)throwsIOException{// 自定义写入逻辑 oos.defaultWriteObject();// 写入默认字段// 写入额外的数据}privatevoidreadObject(ObjectInputStream ois)throwsIOException,ClassNotFoundException{// 自定义读取逻辑 ois.defaultReadObject();// 读取默认字段// 读取额外的数据并初始化}privatevoidreadObjectNoData()throwsObjectStreamException{// 当序列化流缺少该类的数据时调用}
4.1.7 序列化注意事项
  1. 性能考虑:序列化会遍历整个对象图,对大对象图进行序列化可能较慢
  2. 安全考虑:反序列化可能执行恶意代码,始终验证输入来源
  3. 版本兼容:修改类结构可能导致反序列化失败,使用serialVersionUID控制兼容性
  4. 循环引用:ObjectOutputStream可以处理对象间的循环引用
  5. 关闭流:必须确保正确关闭,否则可能数据不完整

第五部分:数据流

5.1 DataInputStream和DataOutputStream

5.1.1 基本概念

DataInputStreamDataOutputStream提供了读写Java基本数据类型(int、long、float、double等)和字符串的方法,使得处理二进制数据更加方便。

5.1.2 特点
  • 平台无关:使用与机器无关的方式读写数据,保证数据在不同平台间可移植
  • 类型安全:可以直接读写基本类型,无需手动转换字节
  • 网络传输:常用于网络协议实现和二进制文件格式处理
5.1.3 构造方法
// 包装另一个输入流DataInputStream dis =newDataInputStream(InputStream in);// 包装另一个输出流DataOutputStream dos =newDataOutputStream(OutputStream out);
5.1.4 核心方法

DataOutputStream(实现了DataOutput接口):

// 写入基本类型voidwriteBoolean(boolean v)voidwriteByte(int v)voidwriteShort(int v)voidwriteChar(int v)voidwriteInt(int v)voidwriteLong(long v)voidwriteFloat(float v)voidwriteDouble(double v)// 写入字符串voidwriteBytes(String s)// 只写入每个字符的低字节voidwriteChars(String s)// 写入完整的字符(每个字符2字节)voidwriteUTF(String str)// 写入UTF-8格式字符串(先写入长度,再写入字节)// 获取写入的字节数intsize()

DataInputStream(实现了DataInput接口):

// 读取基本类型booleanreadBoolean()bytereadByte()shortreadShort()charreadChar()intreadInt()longreadLong()floatreadFloat()doublereadDouble()// 读取字符串StringreadUTF()
5.1.5 使用示例
publicclassDataStreamExample{publicstaticvoidmain(String[] args){String fileName ="data.bin";// 写入不同类型的数据try(DataOutputStream dos =newDataOutputStream(newFileOutputStream(fileName))){ dos.writeInt(100); dos.writeDouble(3.14159); dos.writeBoolean(true); dos.writeUTF("Hello World"); dos.writeChar('A');System.out.println("总共写入 "+ dos.size()+" 字节");}catch(IOException e){ e.printStackTrace();}// 按相同顺序读取try(DataInputStream dis =newDataInputStream(newFileInputStream(fileName))){int intValue = dis.readInt();double doubleValue = dis.readDouble();boolean boolValue = dis.readBoolean();String strValue = dis.readUTF();char charValue = dis.readChar();System.out.println("读取结果:");System.out.println("int: "+ intValue);System.out.println("double: "+ doubleValue);System.out.println("boolean: "+ boolValue);System.out.println("String: "+ strValue);System.out.println("char: "+ charValue);}catch(IOException e){ e.printStackTrace();}}}
5.1.6 UTF-8字符串格式说明

writeUTF()方法写入的字符串格式有特定结构:

  • 前两个字节:字符串长度(以字节为单位)
  • 后续字节:字符串的Modified UTF-8编码

Modified UTF-8与标准UTF-8的区别:

  • 空字符(‘\u0000’)编码为两个字节(0xC0, 0x80)而不是一个字节
  • 只支持1字节、2字节和3字节的字符(基本多语言平面)
  • 增补字符(surrogate pairs)编码为两个3字节序列

因此,用readUTF()读取时必须使用writeUTF()写入的格式。


第六部分:管道流

6.1 PipedInputStream和PipedOutputStream

6.1.1 基本概念

PipedInputStreamPipedOutputStream用于在同一JVM中的不同线程之间建立通信管道。一个线程通过PipedOutputStream写入数据,另一个线程通过PipedInputStream读取数据。

6.1.2 特点
  • 线程间通信:专为多线程环境设计
  • 阻塞操作:读空或写满时会阻塞线程
  • 有限缓冲区:默认缓冲区大小为1024字节
  • 一对一连接:一个输入流只能连接一个输出流
6.1.3 构造方法
// 创建未连接的管道PipedInputStream pis =newPipedInputStream();PipedOutputStream pos =newPipedOutputStream();// 创建时连接PipedInputStream pis =newPipedInputStream(PipedOutputStream src);PipedOutputStream pos =newPipedOutputStream(PipedInputStream snk);// 指定缓冲区大小PipedInputStream pis =newPipedInputStream(int pipeSize);PipedInputStream pis =newPipedOutputStream(PipedOutputStream src,int pipeSize);
6.1.4 连接方法
// 连接输入流和输出流 pis.connect(PipedOutputStream src); pos.connect(PipedInputStream snk);
6.1.5 使用示例

基本管道通信

publicclassPipeExample{publicstaticvoidmain(String[] args){try{// 创建管道流PipedOutputStream pos =newPipedOutputStream();PipedInputStream pis =newPipedInputStream(pos);// 写入线程Thread writerThread =newThread(()->{try{String[] messages ={"消息1","消息2","消息3","结束"};for(String msg : messages){ pos.write(msg.getBytes(StandardCharsets.UTF_8)); pos.write('\n');// 添加换行符作为分隔System.out.println("写入: "+ msg);Thread.sleep(1000);// 模拟延迟} pos.close();// 关闭输出流,告诉读取线程数据结束}catch(IOException|InterruptedException e){ e.printStackTrace();}});// 读取线程Thread readerThread =newThread(()->{try{BufferedReader reader =newBufferedReader(newInputStreamReader(pis,StandardCharsets.UTF_8));String line;while((line = reader.readLine())!=null){System.out.println("读取: "+ line);} pis.close();}catch(IOException e){ e.printStackTrace();}}); writerThread.start(); readerThread.start();// 等待线程完成 writerThread.join(); readerThread.join();}catch(IOException|InterruptedException e){ e.printStackTrace();}}}

处理管道断开

publicclassPipeDisconnectHandling{publicstaticvoidmain(String[] args){try{PipedOutputStream pos =newPipedOutputStream();PipedInputStream pis =newPipedInputStream(pos);// 写入少量数据后关闭Thread writer =newThread(()->{try{ pos.write("部分数据".getBytes()); pos.close();// 提前关闭}catch(IOException e){ e.printStackTrace();}});// 延迟读取Thread reader =newThread(()->{try{Thread.sleep(2000);// 等待写入线程结束byte[] buffer =newbyte[1024];int bytesRead = pis.read(buffer);if(bytesRead >0){System.out.println("读取到: "+newString(buffer,0, bytesRead));}// 尝试再次读取 bytesRead = pis.read(buffer);System.out.println("第二次读取结果: "+ bytesRead);// 应为-1}catch(IOException|InterruptedException e){System.err.println("管道错误: "+ e.getMessage());}finally{try{ pis.close();}catch(IOException e){ e.printStackTrace();}}}); writer.start(); reader.start();}catch(IOException e){ e.printStackTrace();}}}
6.1.6 注意事项
  1. 必须在多线程环境使用:单线程中使用可能导致死锁
  2. 避免死锁:读写线程互相等待可能导致死锁
  3. 缓冲区满:默认缓冲区较小,大数据传输时需调整
  4. 流关闭:正确关闭流,避免资源泄漏

第七部分:回退输入流

7.1 PushbackInputStream

7.1.1 基本概念

PushbackInputStream是一个特殊的输入流装饰器,允许将读取的字节推回到流中,以便重新读取。这在解析器实现中特别有用,比如需要预读数据来决定下一步操作。

7.1.2 工作原理

PushbackInputStream内部维护一个回退缓冲区(字节数组)。当调用unread()方法时,数据被写入这个缓冲区。读取操作优先从回退缓冲区读取数据,只有当缓冲区为空时才从底层输入流读取。

7.1.3 构造方法
// 使用默认回退缓冲区大小(1字节)PushbackInputStream pbis =newPushbackInputStream(InputStream in);// 指定回退缓冲区大小PushbackInputStream pbis =newPushbackInputStream(InputStream in,int size);
7.1.4 核心方法
// 推回一个字节(可以多次调用)voidunread(int b)throwsIOException// 推回整个字节数组voidunread(byte[] b)throwsIOException// 推回字节数组的一部分voidunread(byte[] b,int off,int len)throwsIOException// 可用字节数(回退缓冲区中的字节数 + 底层流可用字节数)intavailable()throwsIOException
7.1.5 使用示例

简单的解析器示例

publicclassPushbackParserExample{publicstaticvoidmain(String[] args){String data ="123+456";try(PushbackInputStream pbis =newPushbackInputStream(newByteArrayInputStream(data.getBytes()),2)){// 读取第一个数字int num1 =readNumber(pbis);System.out.println("第一个数字: "+ num1);// 预读运算符int next = pbis.read();if(next =='+'){System.out.println("遇到加号运算符");// 读取第二个数字int num2 =readNumber(pbis);System.out.println("第二个数字: "+ num2);System.out.println("结果: "+(num1 + num2));}else{// 如果不是运算符,推回流中 pbis.unread(next);}}catch(IOException e){ e.printStackTrace();}}// 读取连续的数字字符,返回整数privatestaticintreadNumber(PushbackInputStream pbis)throwsIOException{StringBuilder sb =newStringBuilder();int b;while((b = pbis.read())!=-1){if(b >='0'&& b <='9'){ sb.append((char) b);}else{// 遇到非数字字符,推回流中 pbis.unread(b);break;}}return sb.length()>0?Integer.parseInt(sb.toString()):0;}}

复杂的分词器示例

publicclassSimpleTokenizer{privatePushbackInputStream input;privatestaticfinalint BUFFER_SIZE =1024;publicSimpleTokenizer(InputStream input){this.input =newPushbackInputStream(input, BUFFER_SIZE);}publicStringnextToken()throwsIOException{skipWhitespace();int b = input.read();if(b ==-1){returnnull;// EOF}// 处理不同类型的tokenif(isDigit(b)){returnreadNumber(b);}elseif(isLetter(b)){returnreadWord(b);}elseif(isOperator(b)){returnreadOperator(b);}else{// 单个字符tokenreturnString.valueOf((char) b);}}privateStringreadNumber(int firstDigit)throwsIOException{StringBuilder sb =newStringBuilder(); sb.append((char) firstDigit);while(true){int b = input.read();if(b ==-1||!isDigit(b)){if(b !=-1){ input.unread(b);// 推回非数字字符}break;} sb.append((char) b);}return sb.toString();}privateStringreadWord(int firstLetter)throwsIOException{StringBuilder sb =newStringBuilder(); sb.append((char) firstLetter);while(true){int b = input.read();if(b ==-1||!isLetterOrDigit(b)){if(b !=-1){ input.unread(b);}break;} sb.append((char) b);}return sb.toString();}privateStringreadOperator(int firstChar)throwsIOException{// 处理多字符运算符,如 ==, !=, <=, >= 等StringBuilder sb =newStringBuilder(); sb.append((char) firstChar);int next = input.read();if(next !=-1){String possibleOp = sb.toString()+(char) next;if(isMultiCharOperator(possibleOp)){ sb.append((char) next);}else{ input.unread(next);}}return sb.toString();}privatevoidskipWhitespace()throwsIOException{int b;while((b = input.read())!=-1&&Character.isWhitespace(b)){// 跳过空白字符}if(b !=-1){ input.unread(b);}}privatebooleanisDigit(int b){return b >='0'&& b <='9';}privatebooleanisLetter(int b){return(b >='a'&& b <='z')||(b >='A'&& b <='Z');}privatebooleanisLetterOrDigit(int b){returnisLetter(b)||isDigit(b)|| b =='_';}privatebooleanisOperator(int b){return b =='+'|| b =='-'|| b =='*'|| b =='/'|| b =='='|| b =='<'|| b =='>'|| b =='!';}privatebooleanisMultiCharOperator(String op){return op.equals("==")|| op.equals("!=")|| op.equals("<=")|| op.equals(">=");}publicstaticvoidmain(String[] args){String code ="if (x <= 100) { result = 42; }";try(InputStream is =newByteArrayInputStream(code.getBytes())){SimpleTokenizer tokenizer =newSimpleTokenizer(is);String token;while((token = tokenizer.nextToken())!=null){System.out.println("Token: "+ token);}}catch(IOException e){ e.printStackTrace();}}}
7.1.6 性能考虑
  • 回退缓冲区使用从后往前填充的方式,这样读取时可以从前往后访问
  • 缓冲区满时会抛出IOException
  • 频繁的unread操作会增加内存复制开销

第八部分:打印流

8.1 PrintStream

8.1.1 基本概念

PrintStream是一个功能丰富的输出流装饰器,提供了打印各种数据类型的便利方法。最著名的PrintStream实例就是System.outSystem.err

8.1.2 特点
  • 便利的输出方法:提供重载的print()println()方法
  • 格式化输出:支持printf()format()方法
  • 不抛出IOException:异常被内部捕获,可通过checkError()检查状态
  • 自动刷新:可配置为遇到换行符时自动刷新
  • 字符编码:支持指定字符编码
8.1.3 构造方法
// 包装OutputStreamPrintStream ps =newPrintStream(OutputStream out);PrintStream ps =newPrintStream(OutputStream out,boolean autoFlush);PrintStream ps =newPrintStream(OutputStream out,boolean autoFlush,String encoding);// 直接创建文件流PrintStream ps =newPrintStream(String fileName);PrintStream ps =newPrintStream(String fileName,String encoding);PrintStream ps =newPrintStream(File file);
8.1.4 核心方法

打印方法

// 打印不换行voidprint(boolean b)voidprint(char c)voidprint(int i)voidprint(long l)voidprint(float f)voidprint(double d)voidprint(char[] s)voidprint(String s)voidprint(Object obj)// 打印并换行voidprintln()voidprintln(boolean x)// ... 各种类型的println重载// 格式化输出PrintStreamprintf(String format,Object... args)PrintStreamformat(String format,Object... args)

错误检查

// 检查是否发生错误booleancheckError()// 清除错误状态(protected方法)protectedvoidclearError()
8.1.5 使用示例

基本输出

publicclassPrintStreamBasicExample{publicstaticvoidmain(String[] args){try(PrintStream ps =newPrintStream("output.txt","UTF-8")){// 各种打印方法 ps.print(true); ps.print(' '); ps.print(123); ps.print(' '); ps.print(3.14159); ps.println();// 换行 ps.println("这是一行文本"); ps.println(newObject());// 格式化输出 ps.printf("姓名:%s,年龄:%d,身高:%.2f米%n","张三",25,1.75);// 格式化并返回PrintStream,支持链式调用 ps.format("圆周率:%.10f%n",Math.PI).format("当前时间:%tF %<tT%n",newDate());// 检查错误状态if(ps.checkError()){System.err.println("写入过程中发生错误");}}catch(IOException e){ e.printStackTrace();}}}

重定向System.out

publicclassRedirectSystemOutExample{publicstaticvoidmain(String[] args){// 保存原始System.outPrintStream originalOut =System.out;try{// 创建文件输出流PrintStream fileOut =newPrintStream(newFileOutputStream("console.log",true),true,// 自动刷新"UTF-8");// 重定向System.outSystem.setOut(fileOut);// 这些输出会写入文件System.out.println("这是写入文件的第一行");System.out.printf("当前时间:%tF %<tT%n",newDate());// 创建日志记录logMessage("系统启动");logMessage("用户登录");System.out.println("写入完成");}catch(IOException e){ e.printStackTrace();}finally{// 恢复原始System.outSystem.setOut(originalOut);System.out.println("已恢复控制台输出");}}privatestaticvoidlogMessage(String message){// 此时System.out已被重定向System.out.printf("[%tF %<tT] %s%n",newDate(), message);}}

自定义日志类

publicclassLogger{privatePrintStream out;privatePrintStream err;privateboolean debugEnabled;publicLogger(String logFile)throwsIOException{// 普通日志输出到文件this.out =newPrintStream(newFileOutputStream(logFile,true),true,"UTF-8");// 错误日志同时输出到文件和标准错误this.err =newPrintStream(newTeeOutputStream(System.err,newFileOutputStream(logFile +".err",true)),true,"UTF-8");}publicvoidinfo(String format,Object... args){ out.printf("[INFO] "+ format +"%n", args);}publicvoiderror(String format,Object... args){ err.printf("[ERROR] "+ format +"%n", args);}publicvoiddebug(String format,Object... args){if(debugEnabled){ out.printf("[DEBUG] "+ format +"%n", args);}}publicvoidsetDebugEnabled(boolean enabled){this.debugEnabled = enabled;}publicvoidclose(){ out.close(); err.close();}// 辅助类:将输出同时写入两个流privatestaticclassTeeOutputStreamextendsOutputStream{privateOutputStream out1;privateOutputStream out2;publicTeeOutputStream(OutputStream out1,OutputStream out2){this.out1 = out1;this.out2 = out2;}@Overridepublicvoidwrite(int b)throwsIOException{ out1.write(b); out2.write(b);}@Overridepublicvoidflush()throwsIOException{ out1.flush(); out2.flush();}@Overridepublicvoidclose()throwsIOException{try{ out1.close();}finally{ out2.close();}}}}
8.1.6 重要注意事项
  1. 异常处理:PrintStream不会抛出IOException,必须通过checkError()检查错误
  2. 性能考虑:频繁的print()调用仍然会加锁,多线程环境下可以考虑缓冲
  3. 自动刷新:启用autoFlush时,写入字节数组、调用println()或遇到’\n’时会自动刷新
  4. 字符编码:处理非ASCII字符时必须指定正确的编码

第九部分:过滤流

9.1 FilterInputStream和FilterOutputStream

9.1.1 基本概念

FilterInputStreamFilterOutputStream是装饰器模式中的装饰者基类。它们本身不添加额外功能,只是简单地将所有方法调用转发给底层流。所有装饰器类(如BufferedInputStream、DataInputStream)都继承自这些过滤流。

9.1.2 自定义过滤流示例
// 自定义过滤流:将读取的字节转换为大写publicclassUpperCaseInputStreamextendsFilterInputStream{protectedUpperCaseInputStream(InputStream in){super(in);}@Overridepublicintread()throwsIOException{int b =super.read();return(b ==-1)?-1:Character.toUpperCase(b);}@Overridepublicintread(byte[] b,int off,int len)throwsIOException{int bytesRead =super.read(b, off, len);if(bytesRead >0){for(int i = off; i < off + bytesRead; i++){ b[i]=(byte)Character.toUpperCase(b[i]&0xFF);}}return bytesRead;}}// 自定义过滤流:计算写入数据的CRC32校验和publicclassCRCOutputStreamextendsFilterOutputStream{privateCRC32 crc =newCRC32();privatelong bytesWritten =0;publicCRCOutputStream(OutputStream out){super(out);}@Overridepublicvoidwrite(int b)throwsIOException{ out.write(b); crc.update(b); bytesWritten++;}@Overridepublicvoidwrite(byte[] b,int off,int len)throwsIOException{ out.write(b, off, len); crc.update(b, off, len); bytesWritten += len;}publiclonggetCRCValue(){return crc.getValue();}publiclonggetBytesWritten(){return bytesWritten;}}// 使用示例publicclassCustomFilterExample{publicstaticvoidmain(String[] args){String data ="hello world";// 使用UpperCaseInputStreamtry(InputStream is =newUpperCaseInputStream(newByteArrayInputStream(data.getBytes()))){byte[] buffer =newbyte[1024];int bytesRead = is.read(buffer);System.out.println("大写转换结果: "+newString(buffer,0, bytesRead));}catch(IOException e){ e.printStackTrace();}// 使用CRCOutputStreamtry(CRCOutputStream cos =newCRCOutputStream(newFileOutputStream("test.dat"))){ cos.write(data.getBytes()); cos.write("\n第二行".getBytes());System.out.println("写入字节数: "+ cos.getBytesWritten());System.out.println("CRC32校验和: "+Long.toHexString(cos.getCRCValue()));}catch(IOException e){ e.printStackTrace();}}}

第十部分:序列输入流

10.1 SequenceInputStream

10.1.1 基本概念

SequenceInputStream可以将多个输入流连接成一个输入流,按顺序读取每个流,直到所有流都读完。这在处理分段文件、合并多个数据源时非常有用。

10.1.2 构造方法
// 使用枚举SequenceInputStream sis =newSequenceInputStream(Enumeration<?extendsInputStream> e);// 使用两个流SequenceInputStream sis =newSequenceInputStream(InputStream s1,InputStream s2);
10.1.3 使用示例
publicclassSequenceInputStreamExample{publicstaticvoidmain(String[] args){// 准备多个数据源String[] data ={"第一部分数据\n","第二部分数据\n","第三部分数据\n"};List<InputStream> streams =newArrayList<>();for(String d : data){ streams.add(newByteArrayInputStream(d.getBytes()));}// 创建枚举Enumeration<InputStream> enumeration =Collections.enumeration(streams);// 合并流try(SequenceInputStream sis =newSequenceInputStream(enumeration);FileOutputStream fos =newFileOutputStream("combined.txt")){byte[] buffer =newbyte[8192];int bytesRead;while((bytesRead = sis.read(buffer))!=-1){ fos.write(buffer,0, bytesRead);}System.out.println("所有数据已合并到 combined.txt");}catch(IOException e){ e.printStackTrace();}// 读取合并后的文件验证try(FileInputStream fis =newFileInputStream("combined.txt")){byte[] content = fis.readAllBytes();System.out.println("合并文件内容:\n"+newString(content));}catch(IOException e){ e.printStackTrace();}}}
10.1.4 注意事项
  • 关闭SequenceInputStream会自动关闭所有包含的输入流
  • 适合处理流式数据,不适合随机访问
  • 可以用于分割文件的重新组装

第十一部分:校验和流

11.1 CheckedInputStream和CheckedOutputStream

11.1.1 基本概念

CheckedInputStreamCheckedOutputStream是Java标准库中用于计算数据校验和的过滤流。它们在读写数据的同时更新校验和,常用于数据完整性验证。

11.1.2 构造方法
// 需要指定Checksum实现CheckedInputStream cis =newCheckedInputStream(InputStream in,Checksum cksum);CheckedOutputStream cos =newCheckedOutputStream(OutputStream out,Checksum cksum);// 获取校验和ChecksumgetChecksum()
11.1.3 Checksum实现类
  • CRC32:32位循环冗余校验
  • Adler32:比CRC32更快但可靠性略低的校验算法
  • 可自定义实现Checksum接口
11.1.4 使用示例
importjava.util.zip.CRC32;importjava.util.zip.Adler32;importjava.util.zip.CheckedInputStream;importjava.util.zip.CheckedOutputStream;publicclassChecksumExample{// 计算文件的CRC32校验和publicstaticlongcalculateCRC32(String filePath)throwsIOException{try(CheckedInputStream cis =newCheckedInputStream(newFileInputStream(filePath),newCRC32())){byte[] buffer =newbyte[8192];while(cis.read(buffer)!=-1){// 持续读取,校验和在内部自动更新}return cis.getChecksum().getValue();}}// 复制文件并同时计算校验和publicstaticlongcopyWithChecksum(String source,String dest)throwsIOException{try(CheckedInputStream cis =newCheckedInputStream(newFileInputStream(source),newCRC32());FileOutputStream fos =newFileOutputStream(dest)){byte[] buffer =newbyte[8192];int bytesRead;while((bytesRead = cis.read(buffer))!=-1){ fos.write(buffer,0, bytesRead);}return cis.getChecksum().getValue();}}// 写入数据并计算校验和publicstaticvoidwriteWithChecksum(String filePath,byte[] data)throwsIOException{try(CheckedOutputStream cos =newCheckedOutputStream(newFileOutputStream(filePath),newAdler32())){ cos.write(data); cos.flush();long checksum = cos.getChecksum().getValue();System.out.printf("写入完成,Adler32校验和: 0x%x%n", checksum);// 将校验和单独保存try(FileOutputStream ckFile =newFileOutputStream(filePath +".cksum")){DataOutputStream dos =newDataOutputStream(ckFile); dos.writeLong(checksum);}}}publicstaticvoidmain(String[] args){try{String testFile ="testdata.bin";// 生成测试数据byte[] data =newbyte[1024*1024];// 1MBnewRandom().nextBytes(data);// 写入数据并计算校验和writeWithChecksum(testFile, data);// 读取文件并验证校验和long calculatedCRC =calculateCRC32(testFile);System.out.printf("读取文件的CRC32校验和: 0x%x%n", calculatedCRC);// 复制文件并验证校验和是否一致String copyFile ="copy_"+ testFile;long copyCRC =copyWithChecksum(testFile, copyFile);System.out.printf("复制文件的CRC32校验和: 0x%x%n", copyCRC);if(calculatedCRC == copyCRC){System.out.println("校验和一致,文件复制正确");}else{System.out.println("校验和不一致,文件可能损坏");}}catch(IOException e){ e.printStackTrace();}}}

第十二部分:压缩流

12.1 GZIPInputStream和GZIPOutputStream

12.1.1 基本概念

Java的java.util.zip包提供了多种压缩和解压缩流,其中最常用的是GZIPInputStreamGZIPOutputStream,它们实现了GZIP文件格式的压缩和解压缩。

12.1.2 构造方法
// 压缩输出流GZIPOutputStream gzos =newGZIPOutputStream(OutputStream out);GZIPOutputStream gzos =newGZIPOutputStream(OutputStream out,int size);// 指定缓冲区大小// 解压缩输入流GZIPInputStream gzis =newGZIPInputStream(InputStream in);GZIPInputStream gzis =newGZIPInputStream(InputStream in,int size);
12.1.3 使用示例
importjava.util.zip.GZIPInputStream;importjava.util.zip.GZIPOutputStream;publicclassGZIPExample{// 压缩文件publicstaticvoidcompressFile(String sourceFile,String gzipFile)throwsIOException{try(FileInputStream fis =newFileInputStream(sourceFile);FileOutputStream fos =newFileOutputStream(gzipFile);GZIPOutputStream gzos =newGZIPOutputStream(fos)){byte[] buffer =newbyte[8192];int bytesRead;while((bytesRead = fis.read(buffer))!=-1){ gzos.write(buffer,0, bytesRead);}// GZIPOutputStream需要finish()来写入压缩数据尾部 gzos.finish();System.out.println("文件已压缩: "+ gzipFile);}}// 解压缩文件publicstaticvoiddecompressFile(String gzipFile,String outputFile)throwsIOException{try(GZIPInputStream gzis =newGZIPInputStream(newFileInputStream(gzipFile));FileOutputStream fos =newFileOutputStream(outputFile)){byte[] buffer =newbyte[8192];int bytesRead;while((bytesRead = gzis.read(buffer))!=-1){ fos.write(buffer,0, bytesRead);}System.out.println("文件已解压缩: "+ outputFile);}}// 压缩数据流publicstaticbyte[]compressData(byte[] data)throwsIOException{ByteArrayOutputStream baos =newByteArrayOutputStream();try(GZIPOutputStream gzos =newGZIPOutputStream(baos)){ gzos.write(data); gzos.finish();}return baos.toByteArray();}// 解压缩数据流publicstaticbyte[]decompressData(byte[] compressedData)throwsIOException{ByteArrayInputStream bais =newByteArrayInputStream(compressedData);ByteArrayOutputStream baos =newByteArrayOutputStream();try(GZIPInputStream gzis =newGZIPInputStream(bais)){byte[] buffer =newbyte[8192];int bytesRead;while((bytesRead = gzis.read(buffer))!=-1){ baos.write(buffer,0, bytesRead);}}return baos.toByteArray();}publicstaticvoidmain(String[] args){try{// 创建测试文件String testFile ="test.txt";try(FileWriter fw =newFileWriter(testFile)){for(int i =0; i <10000; i++){ fw.write("这是一行测试文本,用于演示GZIP压缩功能。\n");}}// 压缩文件String gzipFile ="test.txt.gz";compressFile(testFile, gzipFile);// 检查压缩效果File original =newFile(testFile);File compressed =newFile(gzipFile);System.out.printf("原始大小: %d bytes%n", original.length());System.out.printf("压缩后大小: %d bytes%n", compressed.length());System.out.printf("压缩率: %.2f%%%n",(1-(double)compressed.length()/ original.length())*100);// 解压缩文件String decompressedFile ="decompressed.txt";decompressFile(gzipFile, decompressedFile);// 验证解压缩后内容相同String originalContent =newString(Files.readAllBytes(original.toPath()));String decompressedContent =newString(Files.readAllBytes(newFile(decompressedFile).toPath()));System.out.println("内容相同: "+ originalContent.equals(decompressedContent));}catch(IOException e){ e.printStackTrace();}}}

12.2 ZipInputStream和ZipOutputStream

12.2.1 基本概念

ZipInputStreamZipOutputStream用于处理ZIP格式的压缩文件,支持包含多个条目的ZIP档案。

12.2.2 使用示例
importjava.util.zip.*;publicclassZipExample{// 创建ZIP文件publicstaticvoidcreateZipFile(String zipFileName,Map<String,byte[]> files)throwsIOException{try(ZipOutputStream zos =newZipOutputStream(newFileOutputStream(zipFileName))){for(Map.Entry<String,byte[]> entry : files.entrySet()){// 创建ZIP条目ZipEntry zipEntry =newZipEntry(entry.getKey()); zos.putNextEntry(zipEntry);// 写入文件数据 zos.write(entry.getValue());// 关闭当前条目 zos.closeEntry();}System.out.println("ZIP文件创建完成: "+ zipFileName);}}// 读取ZIP文件publicstaticvoidreadZipFile(String zipFileName)throwsIOException{try(ZipInputStream zis =newZipInputStream(newFileInputStream(zipFileName))){ZipEntry entry;byte[] buffer =newbyte[8192];while((entry = zis.getNextEntry())!=null){System.out.println("读取条目: "+ entry.getName());System.out.println(" 大小: "+ entry.getSize()+" bytes");System.out.println(" 压缩后大小: "+ entry.getCompressedSize()+" bytes");System.out.println(" 方法: "+(entry.getMethod()==ZipEntry.DEFLATED ?"DEFLATED":"STORED"));// 读取条目内容ByteArrayOutputStream baos =newByteArrayOutputStream();int bytesRead;while((bytesRead = zis.read(buffer))!=-1){ baos.write(buffer,0, bytesRead);}System.out.println(" 内容长度: "+ baos.size()+" bytes");System.out.println(); zis.closeEntry();}}}// 使用ZipFile随机访问ZIP条目(推荐用于大量文件)publicstaticvoidreadWithZipFile(String zipFileName)throwsIOException{try(ZipFile zipFile =newZipFile(zipFileName)){Enumeration<?extendsZipEntry> entries = zipFile.entries();while(entries.hasMoreElements()){ZipEntry entry = entries.nextElement();System.out.println("ZIP条目: "+ entry.getName());// 直接获取输入流读取内容try(InputStream is = zipFile.getInputStream(entry)){byte[] content = is.readAllBytes();System.out.println(" 内容大小: "+ content.length);}}}}publicstaticvoidmain(String[] args){try{// 准备要压缩的文件Map<String,byte[]> files =newHashMap<>(); files.put("document.txt","这是文档内容".getBytes(StandardCharsets.UTF_8)); files.put("data.bin",newbyte[1000]); files.put("subdir/notes.txt","这是子目录中的笔记".getBytes(StandardCharsets.UTF_8));// 创建ZIP文件String zipFile ="archive.zip";createZipFile(zipFile, files);// 读取ZIP文件System.out.println("\n使用ZipInputStream读取:");readZipFile(zipFile);System.out.println("\n使用ZipFile读取:");readWithZipFile(zipFile);}catch(IOException e){ e.printStackTrace();}}}

第十三部分:组合流与最佳实践

13.1 流的装饰器模式

Java I/O库广泛使用了装饰器模式(Decorator Pattern),允许动态地为流添加功能。这种设计使得组合不同功能的流变得非常简单。

// 一个典型的组合流示例InputStream input =newBufferedInputStream(// 添加缓冲功能newGZIPInputStream(// 添加解压缩功能newFileInputStream("data.gz")));// 基础文件输入

13.2 常用组合模式

publicclassStreamCombinations{// 高效读取压缩的序列化对象publicstaticObjectreadCompressedObject(String file)throwsIOException,ClassNotFoundException{try(ObjectInputStream ois =newObjectInputStream(newBufferedInputStream(newGZIPInputStream(newFileInputStream(file))))){return ois.readObject();}}// 写入带校验和的压缩数据publicstaticvoidwriteCompressedWithChecksum(String file,byte[] data)throwsIOException{try(CheckedOutputStream cos =newCheckedOutputStream(newGZIPOutputStream(newBufferedOutputStream(newFileOutputStream(file))),newCRC32())){ cos.write(data); cos.flush();System.out.printf("数据CRC32: 0x%x%n", cos.getChecksum().getValue());}}// 网络数据传输组合publicstaticvoidsendObjectOverNetwork(Socket socket,Object obj)throwsIOException{try(ObjectOutputStream oos =newObjectOutputStream(newBufferedOutputStream( socket.getOutputStream()))){ oos.writeObject(obj); oos.flush();}}// 读取配置文件(使用PushbackInputStream处理注释)publicstaticPropertiesreadConfig(String file)throwsIOException{Properties props =newProperties();try(PushbackInputStream pbis =newPushbackInputStream(newBufferedInputStream(newFileInputStream(file)),2)){StringBuilder currentLine =newStringBuilder();int b;while((b = pbis.read())!=-1){// 检查是否是注释行(以#开头)if(b =='#'){// 跳过整行while((b = pbis.read())!=-1&& b !='\n'){// 跳过注释内容}continue;}// 处理非注释行 currentLine.append((char) b);// 如果遇到换行符,处理这一行if(b =='\n'){String line = currentLine.toString().trim();if(!line.isEmpty()){// 解析属性行String[] parts = line.split("=",2);if(parts.length ==2){ props.setProperty(parts[0].trim(), parts[1].trim());}} currentLine =newStringBuilder();}}}return props;}}

13.3 性能优化最佳实践

13.3.1 缓冲区大小选择
publicclassBufferSizeOptimization{// 根据设备特性选择缓冲区大小publicstaticintgetOptimalBufferSize(){// 对于HDD,建议64KB// 对于SSD,建议16KB-32KB// 对于网络流,建议8KB-16KBString os =System.getProperty("os.name").toLowerCase();if(os.contains("linux")){// Linux通常使用4KB页大小,缓冲区设为4KB的倍数return8192;// 8KB}elseif(os.contains("win")){// Windows通常使用64KB的簇大小return65536;// 64KB}else{return16384;// 16KB默认值}}// 智能缓冲流创建publicstaticInputStreamcreateOptimizedInputStream(File file)throwsIOException{int bufferSize =getOptimalBufferSize();returnnewBufferedInputStream(newFileInputStream(file), bufferSize);}}
13.3.2 避免常见陷阱
publicclassCommonPitfalls{// 错误:没有使用缓冲publicstaticvoidbadCopy(File source,File dest)throwsIOException{try(FileInputStream fis =newFileInputStream(source);FileOutputStream fos =newFileOutputStream(dest)){int b;while((b = fis.read())!=-1){// 单字节读取,性能极差 fos.write(b);}}}// 正确:使用缓冲publicstaticvoidgoodCopy(File source,File dest)throwsIOException{try(BufferedInputStream bis =newBufferedInputStream(newFileInputStream(source));BufferedOutputStream bos =newBufferedOutputStream(newFileOutputStream(dest))){byte[] buffer =newbyte[8192];int bytesRead;while((bytesRead = bis.read(buffer))!=-1){ bos.write(buffer,0, bytesRead);}}}// 错误:不正确的编码处理publicstaticvoidbadEncodingHandling(String file)throwsIOException{try(FileInputStream fis =newFileInputStream(file)){byte[] data = fis.readAllBytes();String text =newString(data);// 使用平台默认编码,可能乱码System.out.println(text);}}// 正确:指定编码publicstaticvoidgoodEncodingHandling(String file)throwsIOException{try(FileInputStream fis =newFileInputStream(file)){byte[] data = fis.readAllBytes();String text =newString(data,StandardCharsets.UTF_8);System.out.println(text);}}// 错误:关闭顺序不当publicstaticvoidbadCloseOrder(String file)throwsIOException{GZIPOutputStream gzos =null;FileOutputStream fos =null;try{ fos =newFileOutputStream(file); gzos =newGZIPOutputStream(fos); gzos.write("data".getBytes());}finally{ gzos.close();// 先关闭包装流 fos.close();// 后关闭底层流 - GZIPOutputStream已经关闭了它}}// 正确:try-with-resources自动处理publicstaticvoidgoodCloseOrder(String file)throwsIOException{try(FileOutputStream fos =newFileOutputStream(file);GZIPOutputStream gzos =newGZIPOutputStream(fos)){ gzos.write("data".getBytes());}// 自动关闭,顺序正确(后创建的先关闭)}}

13.4 异常处理最佳实践

publicclassExceptionHandlingBestPractices{// 完整的异常处理模式publicstaticvoidsafeFileCopy(File source,File dest){// 使用try-with-resources自动关闭资源(JDK 7+)try(InputStream in =newBufferedInputStream(newFileInputStream(source));OutputStream out =newBufferedOutputStream(newFileOutputStream(dest))){byte[] buffer =newbyte[8192];int bytesRead;while((bytesRead = in.read(buffer))!=-1){ out.write(buffer,0, bytesRead);} out.flush();// 确保所有数据写入}catch(FileNotFoundException e){System.err.println("文件未找到: "+ e.getMessage());// 根据业务需求处理,可能是创建文件或抛出业务异常}catch(IOException e){System.err.println("I/O错误: "+ e.getMessage());// 记录日志,可能重试或通知用户 e.printStackTrace();}catch(SecurityException e){System.err.println("没有权限访问文件: "+ e.getMessage());// 权限不足的处理}}// 处理部分写入的情况publicstaticvoidrobustWrite(File file,byte[] data)throwsIOException{try(FileOutputStream fos =newFileOutputStream(file)){int written =0;int remaining = data.length;while(remaining >0){int bytesToWrite =Math.min(remaining,8192); fos.write(data, written, bytesToWrite); written += bytesToWrite; remaining -= bytesToWrite;}System.out.printf("成功写入 %d 字节到 %s%n", written, file);}}// 使用finally确保资源关闭(JDK 6及之前版本)publicstaticvoidlegacyResourceHandling(String file){InputStream in =null;try{ in =newFileInputStream(file);// 处理数据}catch(IOException e){ e.printStackTrace();}finally{if(in !=null){try{ in.close();}catch(IOException e){// 记录关闭异常,但不应掩盖原始异常 e.printStackTrace();}}}}}

第十四部分:总结与选择指南

14.1 实现类对比表

实现类类型主要用途特点适用场景
FileInputStream/FileOutputStream节点流文件读写基础文件操作所有文件I/O的基础
BufferedInputStream/BufferedOutputStream处理流缓冲功能提高性能,减少I/O次数所有需要提高性能的场景
ByteArrayInputStream/ByteArrayOutputStream节点流内存操作速度快,无需关闭临时数据存储、数据转换
ObjectInputStream/ObjectOutputStream处理流对象序列化保存和恢复对象对象持久化、RMI
DataInputStream/DataOutputStream处理流基本类型读写平台无关的二进制数据网络协议、二进制文件格式
PipedInputStream/PipedOutputStream节点流线程间通信阻塞式管道通信生产者-消费者模式
PushbackInputStream处理流回退功能允许重新读取语法解析、协议解析
PrintStream处理流格式化输出方便的打印方法日志输出、System.out
SequenceInputStream处理流流合并合并多个输入流文件合并、分段数据处理
GZIPInputStream/GZIPOutputStream处理流数据压缩GZIP格式压缩文件压缩、网络传输优化
ZipInputStream/ZipOutputStream处理流ZIP文件处理支持多条目ZIP档案操作
CheckedInputStream/CheckedOutputStream处理流校验和计算数据完整性验证文件传输验证、存储校验

14.2 选择指南

根据不同的使用场景,推荐以下选择策略:

文件读写场景

  • 小文件:直接使用FileInputStream/FileOutputStream
  • 大文件:使用BufferedInputStream/BufferedOutputStream包装
  • 需要频繁读写:始终使用缓冲流

内存数据处理

  • 临时数据:使用ByteArrayInputStream/ByteArrayOutputStream
  • 数据格式转换:内存流作为中间缓冲区

数据持久化

  • 对象存储:使用ObjectInputStream/ObjectOutputStream
  • 基本类型数据:使用DataInputStream/DataOutputStream
  • 文本数据:使用字符流(Reader/Writer)

网络通信

  • 高效传输:组合BufferedOutputStream和DataOutputStream
  • 对象传输:组合ObjectOutputStream和缓冲流
  • 压缩传输:组合GZIPOutputStream和缓冲流

多线程编程

  • 线程间数据交换:使用PipedInputStream/PipedOutputStream
  • 注意:必须在不同线程中使用,避免死锁

特殊需求

  • 需要预读功能:使用PushbackInputStream
  • 需要格式化输出:使用PrintStream
  • 需要数据验证:使用CheckedInputStream/CheckedOutputStream
  • 需要处理ZIP档案:使用ZipInputStream/ZipOutputStream或ZipFile

14.3 性能考虑要点

  1. 缓冲区大小:8KB-64KB通常是最佳范围
  2. 减少系统调用:每次read()/write()都可能触发系统调用
  3. 批量操作:尽量使用数组参数的read(byte[])write(byte[])方法
  4. 避免频繁创建流:流创建有开销,考虑复用
  5. 及时关闭:使用try-with-resources确保资源释放

14.4 最终建议

Java的I/O体系虽然庞大,但掌握了核心概念和常用实现类后,可以灵活组合出满足各种需求的解决方案。建议:

  1. 优先使用缓冲流提高性能
  2. 始终使用try-with-resources自动管理资源
  3. 根据数据类型选择合适的流(字节流还是字符流)
  4. 注意字符编码问题,始终明确指定编码
  5. 理解装饰器模式,学会组合不同功能的流

Read more

Python 小工具实战:图片水印批量添加工具

Python 小工具实战:图片水印批量添加工具

Python 小工具实战:图片水印批量添加工具 Python 小工具实战:图片水印批量添加工具,本文详细介绍了使用 Python开发 给图片加水印的工具,该工具基于 Pillow 和 tkinter 库构建,可解决单图处理耗时、专业软件操作复杂的问题。工具支持单图与批量处理,用户能自定义水印文字、字体大小、透明度及颜色,还可选择 9 个常用水印位置或设置行列重复分布。新增的全屏水印模式可通过调整旋转角度与间距,生成铺满图片的版权保护水印,且界面采用卡片式布局,搭配浅灰背景与蓝色按钮,简洁美观,底部状态栏实时显示操作进度。文中提供完整可运行代码,并给出参数校验、字体兼容、常见报错解决等实用内容,新手按步骤即可上手,或者直接运行使用。 前言     Python作为一门简洁、易读、功能强大的编程语言,其基础语法是入门学习的核心。掌握好基础语法,能为后续的编程实践打下坚实的基础。本文将全面讲解Python3的基础语法知识,适合编程初学者系统学习。Python以其简洁优雅的语法和强大的通用性,成为当今最受欢迎的编程语言。本专栏旨在系统性地带你从零基础入门到精通Python核心。无论你是

By Ne0inhk
C语言游戏开发:Pygame、SDL、OpenGL深度解析

C语言游戏开发:Pygame、SDL、OpenGL深度解析

C语言游戏开发:Pygame、SDL、OpenGL深度解析 一、前言:为什么游戏开发是C语言开发的重要技能? 学习目标 * 理解游戏开发的本质:编写程序实现游戏逻辑、图形渲染、用户交互 * 明确游戏开发的重要性:支撑游戏产业的发展,成为游戏开发者的必备技能 * 掌握本章学习重点:Pygame、SDL、OpenGL的开发方法、避坑指南、实战案例分析 * 学会使用C语言开发游戏,实现游戏逻辑和用户交互 重点提示 💡 游戏开发是C语言开发的重要技能!随着游戏产业的发展,游戏开发的需求越来越大,C语言的高性能和可移植性使其在游戏开发中具有重要地位。 二、模块1:Pygame游戏开发基础 2.1 学习目标 * 理解Pygame的本质:基于SDL的Python游戏库,简化游戏开发 * 掌握Pygame的核心架构:窗口管理、事件处理、图形渲染、音频播放 * 掌握Pygame的开发方法:使用Pygame库进行游戏开发 * 掌握Pygame的避坑指南:避免窗口创建失败、避免图形渲染错误、避免事件处理错误 * 避开Pygame使用的3大常见坑 2.

By Ne0inhk
Rust深度学习框架Burn 0.20是否能超过python?

Rust深度学习框架Burn 0.20是否能超过python?

提到深度学习,大家脑子里跳出的第一个词肯定是大红大紫的 PyTorch 或者 TensorFlow。虽然 Python 在科研圈呼风唤雨,但到了真正追求极致性能、追求“一次编译,到处运行”的生产环境,Python 的解释器开销和复杂的依赖管理往往让人抓狂。 最近,Rust 圈的深度学习黑马 Burn 发布了 0.20 版本。这不仅仅是一个小版本的迭代,它带来的 CubeK 和 CubeCL 组合拳,直接向我们展示了 Rust 在 AI 基础设施领域的“降维打击”能力。 今天咱们就来拆解一下,为什么 Burn 0.20 值得每一个对性能有追求的开发者关注。 一、 核心痛点:AI 硬件的“碎片化”苦难 在 AI

By Ne0inhk

StarUML(6.3.3)2025-10-24更新!下载、破解、汉化及搭建C++扩展,从0到1全攻略教程(Windows11)

-1#主包作为第一次配StarUML环境可谓是吃进苦头,像无头苍蝇般,这里无偿分享给大家,如何从0到1实现汉化、破解、及解决软件c++扩展下载失败的问题 1.StartUML的下载 1.1官网网址: StarUMLhttps://staruml.io/ 1.2进去后按照此: 1.3然后点击运行,其正常界面如(代表下载成功): 2.StartUML的汉化及破解 2.1找到StartUML的安装目录(如1.2可知,一般在C盘的Program Files里) 在其根目录下找到 resources(如图): 2.2进入resources文件夹,找到 app.asar: 2.3 访问此网址: https://github.com/X1a0He/StarUML-CrackedAndTranslatehttps://github.com/X1a0He/StarUML-CrackedAndTranslate  进去之后点击

By Ne0inhk