Java 中的 I/O 抽象
1 前言
由于在接触 Java 之前使用的语言是 Python,所以在转到 Java 后对 Java 的 I/O 操作各种不习惯。
研究后发现 Java 的 I/O 模型和 Python 的基本上还是一样的,只是在接口的设计上有些区别,主要是为了符合 OOP 的思想吧。
这篇博客的主要内容便是和 Java I/O 相关的总结。
2 字节流
和 Python 一样,Java 中最底层的 I/O 接口处理的是 字节序列, 但是和 Python 中的文件对象一把梭不一样,Java 将输入和输出分别抽象为了两个对象。
处理输入流的 InputStream
和处理输出流的 OutputStream
.
这两个类都是抽象类,因此具体的和输入输出相关的功能将由它们的子类实现,而它们则提供一些基本的接口:
InputStream 提供的部分接口:
方法 作用 abstract int read() 读取一个字节,碰到输入流结尾时返回 -1 int read(byte[] b) 将数据读入提供的字节数组,并返回实际读入的字节数,或者在碰到输入流结尾时返回 -1 int read(byte[] b, int off, int len) 将数据读入提供的字节数组,并返回实际读入的字节数,或者在碰到输入流结尾时返回 -1,写入的范围由 off 和 len 指定 OutputStream 提供的部分接口:
方法 作用 abstract void write() 写入一个字节的数据 void write(byte[] b) 写入一个字节数组的数据 void write(byte[] b, int off, int len) 写入字节数组的数据,数据范围由 off 和 len 指定 void flush() 将缓存的数据全部写入目标
可以看到,抽象方法其实只有 read()
和 write()
, 其他的方法会调用这两个方法,因此子类只需要提供这两个抽象方法的实现就可以了。
2.1 常用实现
Java 中字节流的具体实现很多,但最常用的应该就是和 文件 相关的了,因此在这里将它们列举出来:
FileInputStream 的构造方法:
构造方法 说明 FileInputStream(FileDescriptor fdObj) 根据指定的文件描述符创建输入流 FileInputStream(File file) 根据指定的文件对象创建输入流 FileInputStream(String name) 根据指定的文件名称创建输入流 FileOutputStream 的构造方法和 FileInputStream 的基本相同,不同的是多了两个存在布尔标志的构造方法:
构造方法 说明 FileOutputStream(File file, boolean append) 根据指定的文件对象创建输出流,布尔标志指定是否追加 FileOutputStream(String name, boolean append) 根据指定的文件名称创建输出流,布尔标志指定是否追加
在之前学习 C 和 Python 的过程中便了解到 文件描述符 是一个很有用的东西,通过它可以实现一些很有用的功能。
在 Java 中获取文件描述符可以通过调用 FileInputStream
和 FileOutputStream
对象的 getFD()
方法完成,而标准输入输出的文件描述符则需要通过 FileDescriptor
的静态字段获取。
3 字符流
通过字节流可以完成很多 I/O 操作,但是如果连文本文件的处理都通过字节流来完成的话就太麻烦了。因此,为了解决这样的问题,字符流便诞生了。
字符流 是对 字节流 的一层封装,在 Java 中通过 Reader
和 Writer
这两个抽象类来表示。
和 InputStream 和 OutputStream 一样,具体的功能将由它们的子类实现,而它们则提供一些基本的接口,这里列举出最基本的接口:
Reader:
方法 作用 int read() 读取单个字符,返回值是该字符的码点,到达流的末尾就返回 -1 Writer:
方法 作用 void write(int c) 写入单个字符 void write(String str) 写入字符串 abstract void flush() 将缓存的数据全部写入目标
需要注意的是,这里的 read()
方法和 write()
都不是抽象方法了,因为这两个方法实际上都是调用内部的 字节流 完成工作,因此,只需要相应的字节流实现基本的功能就足够了。
3.1 常用实现
Java 字符流实现中最常用的应该是 InputStreamReader
和 OutputStreamWriter
了,它们的构造方法如下:
Reader 构造方法 | Writer 构造方法 | 说明 |
---|---|---|
InputStreamReader(InputStream in) | OutputStreamWriter(OutputStream out) | 根据默认编码创建字符流 |
InputStreamReader(InputStream in, Charset cs) | OutputStreamWriter(OutputStream out, Charset cs) | 根据指定字符集创建字符流 |
InputStreamReader(InputStream in, CharsetDecoder dec) | OutputStreamWriter(OutputStream out, CharsetEncoder dec) | 根据指定字符集解/编码器创建字符流 |
InputStreamReader(InputStream in, String charsetName) | OutputStreamWriter(OutputStream out, String charsetName) | 根据指定字符集名称创建字符流 |
同时,针对文件操作,Java 提供了这两个类的子类 FileReader
和 FileWriter
, 使用这两个类可以省略手动创建字节流的过程,具体内容可以查看相关文档。
4 缓冲区
I/O 操作的一个常识:频繁的 I/O 操作的效率是很低的,所以我们加一个缓冲区吧!
Java 中我们可以通过 BufferedInputStream
和 BufferedOutputStream
为字节流添加缓冲区,通过 BufferedReader
和 BufferedWriter
为字符流添加缓冲区。
这样一来,一段经典的代码就成型了:
// 字节流 -> 字符流 -> 缓冲区 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("example.txt")));
很完美是不是!
5 各种字节流
虽然说最常用的字节流式文件字节流(大概),但是,输入输出的环境是复杂的,处理文件以外,其他的设备如内存、网络等都可能用到输入输出,而且不同的条件下需要的功能还不一样。
字符流的需求相对来说较为统一,因此一般情况下 InputStreamReader
和 OutputStreamWriter
完全可以一统天下,但是对于 字节流 来说,Java 提供了各种各样的实现。
尤其是 FilterInputStream
和 FilterOutputStream
的子类,它们都有以输出/输出流作为参数的构造方法,因此,我们可以将不同的 Filter
组装起来,得到我们想要的功能。
这是除了 字节-字符-缓冲 以外让我觉得最 Beautiful 的设计,能够灵活的适应各种需求。
这些 Filter
的使用可以查看官方文档或者看看《Java 核心技术卷卷二》的 2.1.3 节,这是相当棒的功能。
6 结语
写完博客回头看,感觉质量有点差……
篇幅太少了,很多东西都没有说清楚,属于适合自己回顾的博客 @_@
另外,很想吐槽的是:作为面向对象的语言,Java 内置的网络库居然没有将 请求 和 响应 这两个对象分开!!!
用起来各种不顺手……