InputStreamReader 用于将字节流转换成字符流,看之前以为是读取2个字节,然后第一个字节往左移8位,然后与第二个字节相加,得到一个2字节的 int,然后强制转换成 char。但看到构造函数中传 Decoder 就知道没这么简单。所以这里来分析下。
InputStreamReader
public class InputStreamReader extends Reader {
private final StreamDecoder sd;
public InputStreamReader(InputStream in, String charsetName)
throws UnsupportedEncodingException
{
super(in);
...
sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
}
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
构造函数
public static StreamDecoder forInputStreamReader(InputStream var0, Object var1, String var2) throws UnsupportedEncodingException {
String var3 = var2;
...
// var0: InputStream
// var1:InputStreamReader
// 通过 String 获得 Charset 对象
return new StreamDecoder(var0, var1, Charset.forName(var3));
...
}
StreamDecoder(InputStream var1, Object var2, Charset var3) {
// 设置读入数据是,出现错误字符或者不兼容字符时,替换这些字符
this(var1, var2, var3.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE));
}
StreamDecoder(InputStream var1, Object var2, CharsetDecoder var3) {
super(var2);
this.isOpen = true; // 字节流是否打开
this.haveLeftoverChar = false; // 是否有剩余的 char 没写,true 的话,leftoverChar 字段就是还没写的 char
this.cs = var3.charset(); // 字符集
this.decoder = var3; // 字符解码器,将字节序列按照字符集转换成16位的 Unicode 序列
if (this.ch == null) {
this.in = var1;
this.ch = null;
this.bb = ByteBuffer.allocate(8192); // 8k 的字节缓冲区
}
this.bb.flip();
}
可以看出它主要就是:
- 根据传入的编码名称创建一个字符编码器
- 创建 8K 大小的字节缓冲区
Read()
从上面的 InputStreamReader 可知,调用 InputStreamReader#read 调用的是 sd.read()。
public int read() throws IOException {
return this.read0();
}
private int read0() throws IOException {
Object var1 = this.lock;
synchronized(this.lock) {
if (this.haveLeftoverChar) { // 之前读取了字节到 leftoverChar 中,直接返回该字节
this.haveLeftoverChar = false;
return this.leftoverChar;
} else { // 从流中读取
// 虽然 read0() 只返回1个字节,但这里每次是读取2个
// 因为一个字符就是2个字节
char[] var2 = new char[2];
// 返回读取的字符个数
int var3 = this.read(var2, 0, 2);
switch(var3) {
case -1: // 流已经读到末尾了
return -1;
case 0:
default:
assert false : var3;
return -1;
case 2: // 读取了2个字符,由于本方法只返回一个,则把另一个存到 leftoverChar 中,下次读取直接返回这个
this.leftoverChar = var2[1];
this.haveLeftoverChar = true;
case 1:
return var2[0]; // 返回读取到的第一个字符
}
}
}
}
可以看出它主要是:
- 创建一个长度为 2 的数组,然后读取数据进来
- 返回数组中的第一个 byte,第二个 byte 用 leftoverChar 存起来。当再次调用 read() 方法时,直接返回 leftoverChar。
看看 1 的 this.read(var2, 0, 2) 是怎么读取的:
public int read(char[] var1, int var2, int var3) throws IOException {
int var4 = var2; // 0
int var5 = var3; // 2
Object var6 = this.lock;
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;
if (this.haveLeftoverChar) { // leftoverChar 有值则先放到数组中
var1[var4] = this.leftoverChar;
++var4;
--var5;
this.haveLeftoverChar = false;
var7 = 1;
if (var5 == 0 || !this.implReady()) {
return var7;
}
}
if (var5 == 1) { // 说明只需要读取一个字节
int var8 = this.read0(); // 重复上面步骤读取一个字节到数组中
if (var8 == -1) {
return var7 == 0 ? -1 : var7;
} else {
var1[var4] = (char)var8; // 将读取的一个字节存进去,就可以返回了
return var7 + 1;
}
} else {
return var7 + this.implRead(var1, var4, var4 + var5); // 继续读取
}
}
} else {
throw new IndexOutOfBoundsException();
}
}
}
它做的事情就是读取字节到传进来的数组中:
- 如果 haveLeftoverChar,则将 leftoverChar 写进数组,如果数组写满了则返回
- 如果还需要读取一个 byte 的话,调用 read0() 来读一个字节,直接返回
- 如果需要读取多个字节的话,调用 implRead(var1, var4, var4 + var5) 方法
看下 implRead(char[], 0, 2)是如何读取2个字符到 char[] 中:
int implRead(char[] var1, int var2, int var3) throws IOException {
assert var3 - var2 > 1;
// 将这个字符数组包装到缓冲区
CharBuffer var4 = CharBuffer.wrap(var1, var2, var3 - var2);
if (var4.position() != 0) {
var4 = var4.slice();
}
boolean var5 = false;
while(true) {
// 解码字节缓冲区 bb 到字符缓冲区 CharBuffer var4 中
CoderResult var6 = this.decoder.decode(this.bb, var4, var5);
if (var6.isUnderflow()) { // 已全部解码
// hasRemaining 表示是否有空间剩余
// var4.position() > 0 && !this.inReady() 表示字符缓冲区有内容并且输入流已经读完了
if (var5 || !var4.hasRemaining() || var4.position() > 0 && !this.inReady()) {
break;
}
// 读取字节
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(); // 返回读取到的个数
}
- 将要保存读出的数据的数组包装到 CharBuffer var4 字符缓冲区
- 调用 readBytes() 方法来读取数据,然后解码字节缓冲区 bb 的数据到字符缓冲区 var4 中
可以看出就是读取数据并解码到字符缓冲区中。我们来看下这个 readBytes() 方法,猜测就是读数据到字节缓冲区 bb 中:
private int readBytes() throws IOException {
//压缩缓冲区,当缓冲区中当前位置已到界限时,则时当前位置归0,界限位置到容量位置
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(); // 即 allocate 的时候分配的 8K
var2 = this.bb.position(); // 首次为0
assert var2 <= var1;
int var3 = var2 <= var1 ? var1 - var2 : 0; // 8K
assert var3 > 0;
// 从底层输入流中读取,首次是最多读取 8K 的数据
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;
}
可以看出它实质是从底层输入流中一次性读取 8K 的数据到字节缓冲数组中。
##总结
- InputStreamReader 组合了一个 StreamDecoder,流的读取等操作都是通过它来完成的
- 构造 InputStreamReader 时就会创建一个 StreamDecoder 对象,根据传入的编码名称创建一个字符编码器,然后创建一个大小为 8K 的字节缓冲数组 bb 用来存储从底层输入流读取的字节
- 读取数据:
+ 将传进来的字符数组包装到 CharBuffer 中
- 从底层输入流中一次性读取字节到缓冲数组 bb 中,读取的大小为 bb 剩余空间的大小(首次为 8K)
- 将读取的字节数组通过 CharsetDecoder 解码到 CharBuffer 中