Java经典I/O操作全解析
立即解锁
发布时间: 2025-08-19 00:10:24 阅读量: 14 订阅数: 28 


深入浅出Java与Android开发
### Java经典I/O操作全解析
在Java编程中,I/O操作是非常重要的一部分,它涉及到数据的读写、对象的序列化与反序列化等多个方面。下面将详细介绍Java经典I/O操作的相关知识。
#### 1. 对象反序列化机制
在反序列化一个对象之前,反序列化机制会先检查对象的类是否实现了`Externalizable`接口。具体流程如下:
```mermaid
graph TD;
A[开始反序列化] --> B{类是否实现Externalizable接口};
B -- 是 --> C[通过公共无参构造函数实例化类];
C -- 成功 --> D[调用readExternal()方法];
B -- 否 --> E{是否存在private readObject(ObjectInputStream)方法};
E -- 是 --> F[调用readObject()方法];
E -- 否 --> G[执行默认反序列化,仅包含非瞬态实例字段];
```
- **实现`Externalizable`接口**:如果类实现了该接口,机制会尝试通过公共无参构造函数实例化类,若成功则调用`readExternal()`方法。
- **未实现`Externalizable`接口**:机制会查找`private readObject(ObjectInputStream)`方法,若该方法不存在,则执行默认反序列化,只包含非瞬态实例字段。
#### 2. PrintStream类
`PrintStream`类在Java的流类中比较特殊,从命名规范上看,它本应被命名为`PrintOutputStream`。以下是它的一些特点:
- **功能**:该类是一个过滤输出流类,它将输入数据项的字符串表示写入底层输出流。
- **字符编码**:使用默认字符编码将字符串的字符转换为字节。由于它不支持不同的字符编码,建议使用等效的`PrintWriter`类,但在处理`System.out`和`System.err`时,需要了解`PrintStream`,因为这两个类字段的类型是`PrintStream`。
- **`print()`和`println()`方法**:`PrintStream`实例有各种`print()`和`println()`方法,用于将整数、浮点值和其他数据项的字符串表示打印到底层输出流。`println()`方法与`print()`方法不同,它会在输出后追加一个行终止符。行终止符不一定是换行符,为了提高可移植性,行分隔符是由系统属性`line.separator`定义的字符序列。例如,在Windows平台上,`System.getProperty("line.separator")`返回实际的回车符(13,符号表示为`\r`),后跟实际的换行符(10,符号表示为`\n`);而在Unix和Linux平台上,只返回实际的换行符。
- **其他特性**:
- 与其他输出流不同,打印流不会重新抛出底层输出流抛出的`IOException`实例,而是设置一个内部标志,可以通过调用`PrintStream`的`boolean checkError()`方法进行测试,若返回`true`则表示有问题。
- 可以创建`PrintStream`对象,使其自动将输出刷新到底层输出流。即写入字节数组、调用`println()`方法之一或写入换行符后,会自动调用`flush()`方法。`System.out`和`System.err`分配的`PrintStream`实例会自动将输出刷新到底层输出流。
- `PrintStream`声明了`PrintStream format(String format, Object... args)`方法用于实现格式化输出,还声明了`printf(String format, Object... args)`便利方法,它委托给`format()`方法。例如,`out.printf(format, args)`与`out.format(format, args)`调用效果相同。
**注意事项**:切勿在要通过`print()`或`println()`方法输出的字符串字面量中硬编码`\n`转义序列,这样做不具有可移植性。当需要输出空行时,最简单的方法是调用`System.out.println()`。
#### 3. 字符集和字符编码的历史
早期计算机和编程语言主要由英语国家的英语程序员创建,他们开发了`ASCII`字符集/编码,它定义了代码点0到127与英语中128个常用字符的标准映射。但`ASCII`对于大多数非英语语言来说是不够的,例如它不支持法语中的变音符号。由于一个字节最多可以表示256个不同的字符,世界各地的开发者开始创建不同的字符集/编码,这些编码包含了128个`ASCII`字符,还额外编码了满足法语、希腊语或俄语等语言需求的字符。
国际标准化组织(ISO)和国际电工委员会(IEC)努力将这些8位字符集/编码标准化,形成了`ISO/IEC 8859`系列子标准,如`ISO/IEC 8859 - 1`(也称为`Latin - 1`),它定义了包含`ASCII`以及涵盖大多数西欧国家字符的字符集/编码;`ISO/IEC 8859 - 2`(也称为`Latin - 2`)定义了涵盖中欧和东欧国家的类似字符集/编码。
然而,尽管有这些努力,众多的字符集/编码仍然存在不足,例如大多数字符集/编码只允许创建英语和另一种语言(或少数其他语言)组合的文档,无法使用`ISO/IEC`字符集/编码创建包含英语、法语、土耳其语、俄语和希腊语字符组合的文档。
为了解决这些问题,国际社会创建并不断发展了`Unicode`,它是一个单一的通用字符集。由于`Unicode`字符比`ISO/IEC`字符大,`Unicode`使用几种可变长度编码方案之一,即`Unicode Transformation Format (UTF)`来高效编码`Unicode`字符。例如,`UTF - 8`用1到4个字节对`Unicode`字符集中的每个字符进行编码,并且与`ASCII`向后兼容。
在`ISO/IEC`字符集的上下文中,字符集和字符编码这两个术语通常可以互换使用,因为代码点就是编码;但在`Unicode`的上下文中,`Unicode`是字符集,`UTF - 8`是`Unicode`字符的几种可能的字符编码之一。
#### 4. Writer和Reader类概述
Java的流类适合处理字节序列,但不适合处理字符序列,因为字节和字符是不同的概念,字节表示8位数据项,而字符表示16位数据项。此外,Java的`char`和`String`类型自然地处理字符而非字节,而且字节流对字符集(整数值与符号之间的映射集,如`Unicode`)及其字符编码(字符集成员与字节序列之间的映射,如`UTF - 8`)一无所知。
如果需要处理字符流,应该利用Java的`writer`和`reader`类,它们专为支持字符I/O而设计(处理`char`而非`byte`),并且考虑了字符编码。
`java.io`包提供了几个`writer`和`reader`类,它们是抽象类`Writer`和`Reader`的子类。以下是`writer`和`reader`类的层次结构:
| 类型 | 子类 |
| ---- | ---- |
| `Writer` | `BufferedWriter`、`CharArrayWriter`、`OutputStreamWriter`、`PipedWriter`、`PrintWriter`、`StringWriter`、`FileWriter`、`FilterWriter`(抽象) |
| `Reader` | `BufferedReader`、`CharArrayReader`、`InputStreamReader`、`PipedReader`、`PushbackReader`、`FileReader`、`StringReader`、`LineNumberReader`、`FilterReader`(抽象) |
虽然`writer`和`reader`类的层次结构与输出流和输入流的对应结构相似,但也存在差异。例如,`FilterWriter`和`FilterReader`是抽象的,而它们对应的`FilterOutputStream`和`FilterInputStream`不是抽象的;`BufferedWriter`和`BufferedReader`不扩展`FilterWriter`和`FilterReader`,而`BufferedOutputStream`和`BufferedInputStream`扩展`FilterOutputStream`和`FilterInputStream`。这一设计变化可能与性能有关,因为文件I/O活动(如将一个文件复制到另一个文件)可能涉及许多`write()`/`read()`方法调用,不子类化`FilterWriter`和`FilterReader`可以使`BufferedWriter`和`BufferedReader`获得更好的性能。
#### 5. Writer和Reader类的具体介绍
- **Writer类**:是所有`writer`子类的超类,与`OutputStream`相比,有以下不同:
- 声明了几个`append()`方法,用于将字符追加到`writer`中。这是因为`Writer`实现了`java.lang.Appendable`接口,该接口与`Formatter`类(用于输出格式化字符串)配合使用。
- 声明了额外的`write()`方法,包括一个方便的`void write(String str)`方法,用于将`String`对象的字符写入`writer`。
- **Reader类**:是所有`reader`子类的超类,与`InputStream`相比,有以下不同:
- 声明了`read(char[])`和`read(char[], int, int)`方法,而不是`read(byte[])`和`read(byte[], int, int)`方法。
- 没有声明`available()`方法。
- 声明了一个`boolean ready()`方法,当保证下一次`read()`调用不会阻塞输入时返回`true`。
- 声明了一个`int read(CharBuffer target)`方法,用于从字符缓冲区读取字符。
#### 6. OutputStreamWriter和InputStreamReader类
- **OutputStreamWriter类**:是一个具体的`Writer`子类,它是输入字符序列和输出字节流之间的桥梁。写入该`writer`的字符根据默认或指定的字符编码被编码为字节。默认字符编码可以通过系统属性`file.encoding`访问。每次调用`OutputStreamWriter`的`write()`方法时,会调用编码器对给定的字符进行编码,生成的字节在写入底层输出流之前会先累积在一个缓冲区中,但传递给`write()`方法的字符不会被缓冲。
- **构造函数**:
- `OutputStreamWriter(OutputStream out)`:创建一个输入字符序列(通过`append()`和`write()`方法传递给`OutputStreamWriter`)和底层输出流`out`之间的桥梁,使用默认字符编码将字符编码为字节。
- `OutputStreamWriter(OutputStream out, String charsetName)`:创建一个输入字符序列和底层输出流`out`之间的桥梁,`charsetName`指定用于将字符编码为字节的字符编码。如果指定的字符编码不受支持,该构造函数会抛出`java.io.UnsupportedEncodingException`。
- **示例代码**:
```java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutputStreamWriterExample {
public static void main(Strin
```
0
0
复制全文
相关推荐









