Java.io之InputStreamReader

InputStreamReader是字节输入流向字符输入流转换的桥梁,InputStreamReader这个名字可以记忆为InputStream’s Reader,调用read()时每次读取一个或者多个字节,根据给定的字符集或者平台默认的字符集将字节转换为字符。

为了获得最好的性能,一般用Bufferedreader装饰InputStreamReader

1
BufferedReader br = new bufferedreader(new InputStreamReader(System.in));

InputStreamReader

InputStreamReader的关键字段是private final StreamDecoder sd, 继续阅读其源码就会发现InputStreamReader的所有读取解码工作都是StreamDecoder完成的。

1
2
3
4
5
6
7
public int read() throws IOException {
return sd.read();
}

public int read(char cbuf[], int offset, int length) throws IOException {
return sd.read(cbuf, offset, length);
}

StreamDecoder

所以我们接下来看一下StreamDecoder的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//缓冲区最小size
private static final int MIN_BYTE_BUFFER_SIZE = 32;
//缓冲区默认size
private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
//线程可见变量 true表示字节流已打开
private volatile boolean isOpen;
//是否已经存储左字符 <见讨论>
private boolean haveLeftoverChar;
//存储左字符
private char leftoverChar;
//信道是否可得
private static volatile boolean channelsAvailable = true;
//字符集
private Charset cs;
//解码器 可以将一个字节序列按照特定的字符集转换成一个16位的Unicode序列
private CharsetDecoder decoder;
//缓冲区
private ByteBuffer bb;
//绑定的字节流
private InputStream in;
//信道
private ReadableByteChannel ch;

StreamDecoder的读取方法们

刚说InputStreamReader.read()其实调用的是StreamDecoder.read(),现在我们来看看StreamDecoder的read()及其相关方法们。

InputStreamReader/inputStream/StreamDecoder三者的关系:

1
2
3
InputStreamReader好比一个做字节转字符业务的公司,
inputStream是其客户,拥有字节数据需要转换成字符,
而StreamDecoder是InputStreamReader公司的产品工具,实际负责将inputStream客户的字节转换为字符。
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189

private int read0() throws IOException {
synchronized(this.lock) {
//上次读取是否有保存LeftoverChar 若有则直接返回
if (this.haveLeftoverChar) {
this.haveLeftoverChar = false;
return this.leftoverChar;
} else {
//直接读取两个字节
char[] var2 = new char[2];
//var3是read的返回值 可能为-1,0,1,2
int var3 = this.read(var2, 0, 2);
switch(var3) {
//-1: EOF
case -1:
return -1;
//0: TODO
case 0:
default:
assert false : var3;

return -1;
//2: 成功读取两个字节 暂存第二个字节
case 2:
this.leftoverChar = var2[1];
this.haveLeftoverChar = true;
//1: 只读取到一个字节 直接返回
case 1:
return var2[0];
}
}
}
}

//var1: 读取数据存放的目标数组 var2: 起始存放位置 var3: 读取字节数
public int read(char[] var1, int var2, int var3) throws IOException {
int var4 = var2;
int var5 = var3;
synchronized(this.lock) {
this.ensureOpen();
if (var4 >= 0 && var4 <= var1.length && var5 >= 0 && var4 + var5 <= var1.length && var4 + var5 >= 0) {
if (var5 == 0) {
return 0;
} else {
byte var7 = 0;
//如果有LeftoverChar暂存 则将其读取出来
if (this.haveLeftoverChar) {
var1[var4] = this.leftoverChar;
//游标自增
++var4;
//剩余读取字节数量自减
--var5;
this.haveLeftoverChar = false;
//已读取字节数
var7 = 1;
//如果读取完毕或者TODO 返回读取字节数1
if (var5 == 0 || !this.implReady()) {
return var7;
}
}

if (var5 == 1) {
int var8 = this.read0();
if (var8 == -1) {
//若EOF 则返回已读取字节数
return var7 == 0 ? -1 : var7;
} else {
//否则将读取的一个字节存储到var1[]中 返回读取字节数2
var1[var4] = (char)var8;
return var7 + 1;
}
} else {
//若var3 > 2, 则调用implRead(dst[], start, end)
return var7 + this.implRead(var1, var4, var4 + var5);
}
}
} else {
throw new IndexOutOfBoundsException();
}
}
}

//var1: 读取数据存储的目标数组 var2: 存储的起始位置 var3: 存储的终止位置
int implRead(char[] var1, int var2, int var3) throws IOException {
assert var3 - var2 > 1;

//CharBuffer wrap(char[] array, int offset, int length)
//将保存解析后的字符数据的数组封装到一个CharBuffer对象
CharBuffer var4 = CharBuffer.wrap(var1, var2, var3 - var2);
if (var4.position() != 0) {
//slice方法会舍去var4缓冲区中游标position指向位置之前的数据
var4 = var4.slice();
}

boolean var5 = false;

//当字节流缓存bb和保存解析后字符数组的CharBuffer都准备好,decoder.decode方法真正负责字符解析
//数据流向: inputStream字节流-->InputStreamReader的bb缓冲区-->StreamDecoder解码到var4
while(true) {
//2.将ByteBuffer也就是bb中的数据通过CharsetDecoder的decode方法不断解码到var4中
CoderResult var6 = this.decoder.decode(this.bb, var4, var5);
if (var6.isUnderflow()) {
if (var5 || !var4.hasRemaining() || var4.position() > 0 && !this.inReady()) {
break;
}
//1.将InputStream中的数据读取到ByteBuffer中,也就是bb中 --> 由readBytes()完成
int var7 = this.readBytes();
if (var7 < 0) {
var5 = true;
if (var4.position() == 0 && !this.bb.hasRemaining()) {
break;
}

this.decoder.reset();
}
} else {
if (var6.isOverflow()) {
assert var4.position() > 0;
break;
}

var6.throwException();
}
}

if (var5) {
this.decoder.reset();
}

if (var4.position() == 0) {
if (var5) {
return -1;
}

assert false;
}

return var4.position();
}

private int readBytes() throws IOException {
this.bb.compact();

int var1;
try {
int var2;
if (this.ch != null) {
var1 = this.ch.read(this.bb);
if (var1 < 0) {
var2 = var1;
return var2;
}
} else {
var1 = this.bb.limit();
var2 = this.bb.position();

assert var2 <= var1;

int var3 = var2 <= var1 ? var1 - var2 : 0;

assert var3 > 0;

//将Inputstream中的字节读取到bb中去
//this.bb.arrayOffset() + var2 : 存放的起始位置
//var3 = var1 - var2 : 读取的字节数
int var4 = this.in.read(this.bb.array(), this.bb.arrayOffset() + var2, var3);
if (var4 < 0) {
int var5 = var4;
return var5;
}

if (var4 == 0) {
throw new IOException("Underlying input stream returned zero bytes");
}

assert var4 <= var3 : "n = " + var4 + ", rem = " + var3;

this.bb.position(var2 + var4);
}
} finally {
this.bb.flip();
}

var1 = this.bb.remaining();

assert var1 != 0 : var1;

return var1;
}

讨论

leftoverChar

英文占一个字节,中文占两个字节,为保证不乱码,每次至少读取两个字节

1
2
char[] var2 = new char[2];
int var3 = this.read(var2, 0, 2);

然而调用read()是读取一个字符,所以先将读取的第二个字节暂存,其也就成为了下次读取的字节的左字节

1
2
this.leftoverChar = var2[1];
this.haveLeftoverChar = true;

下次再调用read()读取一个字节的时候就需要先判断haveLeftoverChar是否为true

1
2
3
4
if (this.haveLeftoverChar) {
this.haveLeftoverChar = false;
return this.leftoverChar;
}

Java中Buffer的相关属性

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class Buffer {

// Invariants: mark <= position <= limit <= capacity

//记录一个position位置坐标
private int mark = -1;
//缓冲区的游标
private int position = 0;
//预读上限
private int limit;
//缓冲区大小
private int capacity;
}

写在最后

decoder.decode()参考阅读

direct buffer & heap buffer

Donate here.