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提供了缓冲区,减少系统调用次数,提高读取性能。

相关文章
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
4月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
134 0
|
1月前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
150 1
|
3月前
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
4月前
|
存储 Java Linux
操作系统层面视角下 Java IO 的演进路径及核心技术变革解析
本文从操作系统层面深入解析Java IO的演进历程,涵盖BIO、NIO、多路复用器及Netty等核心技术。分析各阶段IO模型的原理、优缺点及系统调用机制,探讨Java如何通过底层优化提升并发性能与数据处理效率,全面呈现IO技术的变革路径与发展趋势。
99 2
|
8月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
324 23
|
Java
Java 中 IO 流的分类详解
【10月更文挑战第10天】不同类型的 IO 流具有不同的特点和适用场景,我们可以根据具体的需求选择合适的流来进行数据的输入和输出操作。在实际应用中,还可以通过组合使用多种流来实现更复杂的功能。
377 57
|
9月前
|
缓存 网络协议 Java
JAVA网络IO之NIO/BIO
本文介绍了Java网络编程的基础与历史演进,重点阐述了IO和Socket的概念。Java的IO分为设备和接口两部分,通过流、字节、字符等方式实现与外部的交互。
279 0
|
12月前
|
Java
java 中 IO 流
Java中的IO流是用于处理输入输出操作的机制,主要包括字节流和字符流两大类。字节流以8位字节为单位处理数据,如FileInputStream和FileOutputStream;字符流以16位Unicode字符为单位,如FileReader和FileWriter。这些流提供了读写文件、网络传输等基本功能。
214 10
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
346 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)