Java IO - 字符流
Java IO 系列: 第一篇: Java IO - Stream 输入输出流 第二篇: Java IO - 字符流 第三篇: Java NIO - 基本概念
Reader/Writer
在前文Java IO - Stream 输入输出流中,介绍了 Java IO 包内相关类对字节流的定义与处理。
通常字节并不适合人类阅读,为了方便阅读,计算机领域采用字符集(Character encoding)来定义不同字节组合的含义。
因此,在 IO 包中也提供了一组专门用于处理字符流的类,即 Reader/Writer。
Reader/Writer 的设计思想、实现与 IOStream 完全一致,主要区别在于 IOStream 处理字节,Reader/Writer 处理字符。具体的用法可以参考源码实现。
Encoding/Decoding
本节主要讨论字节-字符转换对性能的影响。
在 Reader/Writer 中,除了装饰器实现外,InputStreamReader 与 OutputStreamWriter 专用于将字节流转换为字符流。
具体实现中,转换通过 StreamDecoder/StreamEncoder 实现,StreamDecoder 继承自 Reader,因此实现了 read 方法。在其内部,真正用于转码的是 CharsetDecoder,这是一个抽象类,不同的编码格式实现了不同的 decoder。
encode/decode 本质上是一种查表操作,根据不同的字符集规则,将字节映射为字符。查表映射本身是一项较为耗时的工作,若在大量数据条件下,无意义的字符转换很容易拖慢系统。
以下通过简单的例子展示编解码操作对性能的影响: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50public class ByteStreamAndCharCompareDemo {
public static void main(String[] args) throws NoSuchAlgorithmException {
ByteStreamAndCharCompareDemo demo = new ByteStreamAndCharCompareDemo();
// 1MB data
int capacity = 1024 * 1024 * 1024;
byte[] bytes = demo.buildBigByteArray(capacity);
// read byte stream cost
long timeStart = System.currentTimeMillis();
demo.consumeInputStreamFromArray(bytes);
long timeEnd = System.currentTimeMillis();
System.out.println("Build byte stream and read out cost: " + (timeEnd - timeStart));
// read char stream cost
timeStart = System.currentTimeMillis();
demo.consumeReaderFromArray(bytes);
timeEnd = System.currentTimeMillis();
System.out.println("Build char stream and read out cost: " + (timeEnd - timeStart));
}
public byte[] buildBigByteArray(int capacity) throws NoSuchAlgorithmException {
byte[] bytes = new byte[capacity];
Random random = SecureRandom.getInstanceStrong();
for (int i=0; i<capacity; i++) {
int v = random.nextInt(255);
bytes[i] = ((byte)v);
}
return bytes;
}
public void consumeInputStreamFromArray(byte[] bytes) {
try(ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) {
while (byteArrayInputStream.read() != -1);
} catch (Exception e) {
e.printStackTrace();
}
}
public void consumeReaderFromArray(byte[] bytes) {
try(InputStreamReader inputStreamReader = new InputStreamReader(new ByteArrayInputStream(bytes))) {
while (inputStreamReader.read() != -1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上简单的例子通过构造一个 1MB 大小的字节数组,分别将之转换为
ByteArrayInputStream
与 InputStreamReader
并全部读出,计算其花费时间,程序输出可见:
1 | Build byte stream and read out cost: 2211 |
显然,转换为字符后读取的时间是直接读取字节的 51.6 倍。
在性能优化中,编解码、序列化都属于很耗时的操作通常会被优先考虑优化。很多时候,REST API 响应中会提供对状态、信息等的描述,描述大都采用 String 类型,因此在调用的序列化过程中,势必存在多次的编码/解码操作。因此,若这类 REST API 用于微服务内部调用,而非与人交互,则相关的描述完全可以采用错误码替代,这样就减少了一定的编解码开销,对于高频调用效果较为显著。
Java IO 系列: 第一篇: Java IO - Stream 输入输出流 第二篇: Java IO - 字符流 第三篇: Java NIO - 基本概念