Java IO流(一):字节流与字符流基础

简介: 本文全面解析Java IO流,涵盖字节流、字符流及其使用场景,帮助开发者理解IO流分类与用途,掌握文件读写、编码转换、异常处理等核心技术,通过实战案例提升IO编程能力。

💡 摘要:你是否曾对Java中众多的IO流类感到困惑?是否分不清什么时候用字节流什么时候用字符流?是否想知道为什么要有这么多相似的流类?

别担心,IO流是Java中处理输入输出的核心机制,理解其分类和用途是掌握Java编程的关键。

本文将带你从IO流的基本概念讲起,理解字节流和字符流的根本区别。然后深入字节流体系,学习FileInputStream、FileOutputStream等类的使用。

接着探索字符流体系,掌握FileReader、FileWriter和字符编码的重要性。最后通过实战案例展示如何正确选择和使用不同的流。从文件复制到编码转换,从性能考虑到异常处理,让你全面掌握Java IO流的基础知识。文末附常见问题解析,助你避免IO编程中的常见陷阱。

一、IO流概述:输入与输出的桥梁

1. 什么是IO流?

流的概念:流是数据传输的抽象,可以看作数据的管道或序列。Java IO流提供了统一的方式来处理各种输入输出操作。

流的分类

text

按数据单位:字节流(8位字节) vs 字符流(16位字符)

按流向:输入流(读取数据) vs 输出流(写入数据)

按功能:节点流(直接操作数据源) vs 处理流(包装其他流)

2. IO流体系结构

Java IO主要类体系

text

InputStream (字节输入流)

├── FileInputStream

├── ByteArrayInputStream

├── ObjectInputStream

└── FilterInputStream

       ├── BufferedInputStream

       ├── DataInputStream

       └── ...


OutputStream (字节输出流)

├── FileOutputStream

├── ByteArrayOutputStream

├── ObjectOutputStream

└── FilterOutputStream

       ├── BufferedOutputStream

       ├── DataOutputStream

       └── ...


Reader (字符输入流)

├── FileReader

├── InputStreamReader

├── CharArrayReader

└── BufferedReader


Writer (字符输出流)

├── FileWriter

├── OutputStreamWriter

├── CharArrayWriter

└── BufferedWriter

二、字节流:处理二进制数据

1. FileInputStream:文件字节输入流

基本用法

java

// 1. 创建文件输入流

FileInputStream fis = null;

try {

   fis = new FileInputStream("test.txt");

   

   // 2. 读取数据

   int data;

   while ((data = fis.read()) != -1) { // 每次读取一个字节

       System.out.print((char) data);  // 转换为字符输出

   }

   

} catch (IOException e) {

   e.printStackTrace();

} finally {

   // 3. 关闭流

   if (fis != null) {

       try {

           fis.close();

       } catch (IOException e) {

           e.printStackTrace();

       }

   }

}

批量读取优化

java

try (FileInputStream fis = new FileInputStream("largefile.bin")) {

   byte[] buffer = new byte[1024]; // 1KB缓冲区

   int bytesRead;

   

   while ((bytesRead = fis.read(buffer)) != -1) {

       // 处理读取的数据

       processData(buffer, bytesRead);

   }

} catch (IOException e) {

   e.printStackTrace();

}

2. FileOutputStream:文件字节输出流

基本用法

java

// 使用try-with-resources自动关闭流

try (FileOutputStream fos = new FileOutputStream("output.txt")) {

   String text = "Hello, Java IO!";

   byte[] bytes = text.getBytes(); // 字符串转换为字节数组

   

   fos.write(bytes);      // 写入整个字节数组

   fos.write('\n');       // 写入单个字节

   fos.write("Another line".getBytes());

   

} catch (IOException e) {

   e.printStackTrace();

}

追加模式

java

// 第二个参数true表示追加模式

try (FileOutputStream fos = new FileOutputStream("log.txt", true)) {

   String logEntry = LocalDateTime.now() + " - User logged in\n";

   fos.write(logEntry.getBytes());

} catch (IOException e) {

   e.printStackTrace();

}

3. 文件复制实战

字节流文件复制

java

public class FileCopy {

   public static void copyFile(String sourcePath, String targetPath) {

       // 使用try-with-resources确保资源关闭

       try (FileInputStream fis = new FileInputStream(sourcePath);

            FileOutputStream fos = new FileOutputStream(targetPath)) {

           

           byte[] buffer = new byte[8192]; // 8KB缓冲区

           int bytesRead;

           

           while ((bytesRead = fis.read(buffer)) != -1) {

               fos.write(buffer, 0, bytesRead); // 写入实际读取的字节数

           }

           

           System.out.println("文件复制完成");

           

       } catch (FileNotFoundException e) {

           System.err.println("文件未找到: " + e.getMessage());

       } catch (IOException e) {

           System.err.println("IO错误: " + e.getMessage());

       }

   }

   

   public static void main(String[] args) {

       copyFile("source.jpg", "copy.jpg");

   }

}

三、字符流:处理文本数据

1. 字符编码的重要性

常见字符编码

  • UTF-8:变长编码,兼容ASCII,支持所有Unicode字符
  • GBK:中文编码标准,兼容GB2312
  • ISO-8859-1:Latin-1编码,西欧语言
  • UTF-16:定长或变长编码,Java内部使用

编码问题示例

java

String text = "中文测试";

try {

   // 不同编码的字节表示

   byte[] utf8Bytes = text.getBytes("UTF-8");

   byte[] gbkBytes = text.getBytes("GBK");

   

   System.out.println("UTF-8字节数: " + utf8Bytes.length); // 12

   System.out.println("GBK字节数: " + gbkBytes.length);    // 8

   

   // 错误编码导致的乱码

   String wrongText = new String(utf8Bytes, "ISO-8859-1");

   System.out.println("乱码: " + wrongText); // 输出乱码

   

} catch (UnsupportedEncodingException e) {

   e.printStackTrace();

}

2. FileReader:文件字符输入流

基本用法

java

// 读取文本文件

try (FileReader reader = new FileReader("text.txt", StandardCharsets.UTF_8)) {

   char[] buffer = new char[1024];

   int charsRead;

   

   while ((charsRead = reader.read(buffer)) != -1) {

       String content = new String(buffer, 0, charsRead);

       System.out.print(content);

   }

   

} catch (IOException e) {

   e.printStackTrace();

}

逐行读取的局限

java

// FileReader没有readLine方法,需要包装为BufferedReader

try (FileReader fr = new FileReader("text.txt");

    BufferedReader br = new BufferedReader(fr)) {

   

   String line;

   while ((line = br.readLine()) != null) {

       System.out.println(line);

   }

   

} catch (IOException e) {

   e.printStackTrace();

}

3. FileWriter:文件字符输出流

基本用法

java

// 写入文本文件

try (FileWriter writer = new FileWriter("output.txt", StandardCharsets.UTF_8)) {

   writer.write("Hello, 世界!\n");

   writer.write("这是第二行\n");

   writer.write("数字: " + 42 + "\n");

   

   // 写入字符数组

   char[] chars = {'A', 'B', 'C'};

   writer.write(chars);

   writer.write('\n');

   

} catch (IOException e) {

   e.printStackTrace();

}

追加模式

java

try (FileWriter writer = new FileWriter("log.txt", StandardCharsets.UTF_8, true)) {

   writer.write(LocalDateTime.now() + " - 日志条目\n");

} catch (IOException e) {

   e.printStackTrace();

}

四、字节流 vs 字符流:正确选择

1. 使用场景对比

字节流适用场景

  • ✅ 二进制文件:图片、视频、音频、PDF等
  • ✅ 网络数据传输
  • ✅ 对象序列化
  • ✅ 加密/压缩数据

字符流适用场景

  • ✅ 文本文件:TXT、XML、JSON、HTML等
  • ✅ 配置文件读写
  • ✅ 日志文件处理
  • ✅ 用户输入输出

2. 编码转换桥梁

InputStreamReader和OutputStreamWriter

java

// 字节流到字符流的转换(指定编码)

try (FileInputStream fis = new FileInputStream("source.txt");

    InputStreamReader isr = new InputStreamReader(fis, "GBK"); // 指定源文件编码

    FileOutputStream fos = new FileOutputStream("target.txt");

    OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8")) { // 指定目标编码

   

   char[] buffer = new char[1024];

   int charsRead;

   

   while ((charsRead = isr.read(buffer)) != -1) {

       osw.write(buffer, 0, charsRead); // 编码转换

   }

   

   System.out.println("文件编码转换完成");

   

} catch (IOException e) {

   e.printStackTrace();

}

五、实战案例:综合应用

1. 文件加密解密

简单的XOR加密

java

public class FileEncryptor {

   public static void encryptFile(String sourcePath, String targetPath, byte key) {

       try (FileInputStream fis = new FileInputStream(sourcePath);

            FileOutputStream fos = new FileOutputStream(targetPath)) {

           

           byte[] buffer = new byte[4096];

           int bytesRead;

           

           while ((bytesRead = fis.read(buffer)) != -1) {

               // 对每个字节进行XOR加密

               for (int i = 0; i < bytesRead; i++) {

                   buffer[i] = (byte) (buffer[i] ^ key); // XOR运算

               }

               fos.write(buffer, 0, bytesRead);

           }

           

       } catch (IOException e) {

           e.printStackTrace();

       }

   }

   

   // 解密使用相同的key(XOR的特性)

   public static void decryptFile(String sourcePath, String targetPath, byte key) {

       encryptFile(sourcePath, targetPath, key); // XOR两次等于原数据

   }

   

   public static void main(String[] args) {

       byte key = 0x55; // 加密密钥

       encryptFile("secret.txt", "encrypted.dat", key);

       decryptFile("encrypted.dat", "decrypted.txt", key);

   }

}

2. 配置文件读取

Properties文件处理

java

public class ConfigReader {

   public static Properties loadConfig(String configPath) {

       Properties props = new Properties();

       

       try (FileInputStream fis = new FileInputStream(configPath);

            InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {

           

           props.load(isr); // 加载properties文件

           

       } catch (IOException e) {

           System.err.println("无法读取配置文件: " + e.getMessage());

       }

       

       return props;

   }

   

   public static void saveConfig(String configPath, Properties props) {

       try (FileOutputStream fos = new FileOutputStream(configPath);

            OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {

           

           props.store(osw, "Application Configuration");

           

       } catch (IOException e) {

           System.err.println("无法保存配置文件: " + e.getMessage());

       }

   }

   

   public static void main(String[] args) {

       Properties config = loadConfig("config.properties");

       String username = config.getProperty("username", "defaultUser");

       int timeout = Integer.parseInt(config.getProperty("timeout", "30"));

       

       System.out.println("用户名: " + username);

       System.out.println("超时: " + timeout + "秒");

   }

}

六、异常处理与资源管理

1. 传统的try-catch-finally

手动资源管理

java

FileInputStream fis = null;

FileOutputStream fos = null;


try {

   fis = new FileInputStream("source.txt");

   fos = new FileOutputStream("target.txt");

   

   byte[] buffer = new byte[1024];

   int bytesRead;

   

   while ((bytesRead = fis.read(buffer)) != -1) {

       fos.write(buffer, 0, bytesRead);

   }

   

} catch (IOException e) {

   System.err.println("IO错误: " + e.getMessage());

} finally {

   // 手动关闭资源

   if (fis != null) {

       try {

           fis.close();

       } catch (IOException e) {

           System.err.println("关闭输入流失败: " + e.getMessage());

       }

   }

   if (fos != null) {

       try {

           fos.close();

       } catch (IOException e) {

           System.err.println("关闭输出流失败: " + e.getMessage());

       }

   }

}

2. try-with-resources(推荐)

自动资源管理

java

// Java 7+ 自动关闭资源

try (FileInputStream fis = new FileInputStream("source.txt");

    FileOutputStream fos = new FileOutputStream("target.txt")) {

   

   byte[] buffer = new byte[1024];

   int bytesRead;

   

   while ((bytesRead = fis.read(buffer)) != -1) {

       fos.write(buffer, 0, bytesRead);

   }

   

} catch (IOException e) {

   System.err.println("IO错误: " + e.getMessage());

}

// 不需要finally块,资源自动关闭

自定义可关闭资源

java

// 实现AutoCloseable接口

class DatabaseConnection implements AutoCloseable {

   public DatabaseConnection(String url) {

       System.out.println("连接数据库: " + url);

   }

   

   public void query(String sql) {

       System.out.println("执行查询: " + sql);

   }

   

   @Override

   public void close() {

       System.out.println("关闭数据库连接");

   }

}


// 使用自定义资源

try (DatabaseConnection conn = new DatabaseConnection("jdbc:mysql://localhost:3306/test")) {

   conn.query("SELECT * FROM users");

} // 自动调用close()方法

七、总结:IO流最佳实践

1. 选择正确的流类型

选择指南

  • 文本文件:使用字符流(FileReader/FileWriter)
  • 二进制文件:使用字节流(FileInputStream/FileOutputStream)
  • 需要指定编码:使用InputStreamReader/OutputStreamWriter
  • 需要缓冲:包装为BufferedInputStream/BufferedReader

2. 性能优化建议

缓冲区大小

java

// 合适的缓冲区大小(通常8KB-64KB)

byte[] buffer = new byte[8192]; // 8KB

char[] charBuffer = new char[8192];


// 对于大文件,可以使用更大的缓冲区

byte[] largeBuffer = new byte[65536]; // 64KB

资源管理

  • ✅ 使用try-with-resources确保资源关闭
  • ✅ 及时关闭不再使用的流
  • ✅ 避免同时打开过多文件

3. 编码处理建议

统一编码规范

java

// 明确指定编码

String text = new String(bytes, StandardCharsets.UTF_8);

byte[] output = text.getBytes(StandardCharsets.UTF_8);


// 读写文件时指定编码

try (InputStreamReader reader = new InputStreamReader(

       new FileInputStream("file.txt"), StandardCharsets.UTF_8)) {

   // 读取内容

}

八、面试高频问题

❓1. 字节流和字符流有什么区别?

:字节流以字节为单位操作数据,适合处理二进制文件。字符流以字符为单位操作数据,适合处理文本文件,会自动处理字符编码。

❓2. 为什么需要字符编码?常见的编码有哪些?

:字符编码定义了字符和字节之间的映射关系。常见编码:UTF-8(推荐)、GBK、ISO-8859-1、UTF-16等。

❓3. try-with-resources有什么优势?

:自动关闭资源,代码更简洁,避免资源泄漏,异常处理更清晰。

❓4. 如何提高IO操作的性能?

:使用缓冲区、选择合适的缓冲区大小、使用NIO、批量读写操作。

❓5. FileInputStream和BufferedInputStream有什么区别?

:FileInputStream是基础的文件字节流,每次读取都要进行系统调用。BufferedInputStream提供了缓冲区,减少系统调用次数,提高读取性能。

相关文章
|
24天前
|
JSON 移动开发 网络协议
Java网络编程:Socket通信与HTTP客户端
本文全面讲解Java网络编程,涵盖TCP与UDP协议区别、Socket编程、HTTP客户端开发及实战案例,助你掌握实时通信、文件传输、聊天应用等场景,附性能优化与面试高频问题解析。
|
24天前
|
存储 缓存 安全
Java集合框架(三):Map体系与ConcurrentHashMap
本文深入解析Java中Map接口体系及其实现类,包括HashMap、ConcurrentHashMap等的工作原理与线程安全机制。内容涵盖哈希冲突解决、扩容策略、并发优化,以及不同Map实现的适用场景,助你掌握高并发编程核心技巧。
|
24天前
|
安全 前端开发 Java
Java包管理与访问控制权限详解
本文深入讲解Java包管理和访问控制,涵盖包的创建与使用、访问权限的四个层级,并结合实战案例分析如何设计合理的包结构和访问权限,帮助开发者提升代码的安全性与可维护性。
|
24天前
|
Java 数据库 C++
Java异常处理机制:try-catch、throws与自定义异常
本文深入解析Java异常处理机制,涵盖异常分类、try-catch-finally使用、throw与throws区别、自定义异常及最佳实践,助你写出更健壮、清晰的代码,提升Java编程能力。
|
18天前
|
SQL 数据库 数据安全/隐私保护
SQL基础:DDL、DML、DCL和TCL的区别与使用
本文详细解析了SQL语言的四大类别:数据定义语言(DDL)、数据操作语言(DML)、数据控制语言(DCL)和事务控制语言(TCL),涵盖每类语句的功能、语法、使用场景及示例。
|
22天前
|
缓存 Java 数据安全/隐私保护
Java动态代理详解
动态代理是Java中一种强大且灵活的设计模式,它允许在运行时创建代理对象,从而实现对目标对象方法的拦截与增强。通过动态代理,开发者可以在不修改原始代码的情况下,增强对象功能,适用于日志记录、事务管理、权限控制等多个场景。
|
22天前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。
|
17天前
|
安全 关系型数据库 MySQL
MySQL安全最佳实践:保护你的数据库
本文深入探讨了MySQL数据库的安全防护体系,涵盖认证安全、访问控制、网络安全、数据加密、审计监控、备份恢复、操作系统安全、应急响应等多个方面。通过具体配置示例,为企业提供了一套全面的安全实践方案,帮助强化数据库安全,防止数据泄露和未授权访问,保障企业数据资产安全。
|
23天前
|
设计模式 缓存 Java
Java设计模式(二):观察者模式与装饰器模式
本文深入讲解观察者模式与装饰器模式的核心概念及实现方式,涵盖从基础理论到实战应用的全面内容。观察者模式实现对象间松耦合通信,适用于事件通知机制;装饰器模式通过组合方式动态扩展对象功能,避免子类爆炸。文章通过Java示例展示两者在GUI、IO流、Web中间件等场景的应用,并提供常见陷阱与面试高频问题解析,助你写出灵活、可维护的代码。
|
24天前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。