#[重学Java基础][JavaIO流][Part.6-2]StreamDecoder和StreamEncoder
#StreamDecoder
这两个类是sun.nio包下的类
用于在输入与输出数据流时对其进行解码与编码操作
在之前的甲骨文官方的JDK中是看不到源码的 只能下载OpenJDK查看
或者在grepcode这个网站在线查看
openJDK在线源码
但是java9 开始 甲骨文官方的jdk也是基于openjdk进行开发
所以jdk1.9中是可以看到此类的原源码的
##源码解析
成员变量
最小字节缓冲区大小
private static final int MIN_BYTE_BUFFER_SIZE = 32;
默认字节缓冲区大小
private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
多线程可见量 校验字节流是否打开
private volatile boolean isOpen = true;
是否保存左字符标志
为了保证读入的字符不乱码 则每次读入不能少于两个字符
如果只想要返回一个字符 那么可以保存左侧字符 等到下次返回
private boolean haveLeftoverChar = false;
左侧位字符
private char leftoverChar;
字符集
private Charset cs;
字符解码器 可以将一个字节序列按照特定的字符集转换成一个16位的Unicode序列
private CharsetDecoder decoder;
字节缓冲
private ByteBuffer bb;
被转换的字节输入流
private InputStream in;
可读取字节信道
private ReadableByteChannel ch;
构造方法 通过传入字节输入流 锁对象 字符集构造StreamDecoder对象
StreamDecoder(InputStream in, Object lock, Charset cs) {
this(in, lock,
cs.newDecoder()
设置读入数据时编码字符集错误时的响应-设置为替换错误字符
.onMalformedInput(CodingErrorAction.REPLACE)
设置读入数据时不兼容的字符内容时的响应-设置为替换错误字符
.onUnmappableCharacter(CodingErrorAction.REPLACE));
}
StreamDecoder(InputStream in, Object lock, CharsetDecoder dec) {
super(lock);
this.cs = dec.charset();
this.decoder = dec;
// 调用NIO方法 如果输入数据速度较快时启用(输入数据是文件输入流)
if (false && in instanceof FileInputStream) {
ch = getChannel((FileInputStream)in);
if (ch != null)
bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);
}
if (ch == null) {
this.in = in;
this.ch = null;
bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
}
bb.flip(); // So that bb is initially empty
}
StreamDecoder(ReadableByteChannel ch, CharsetDecoder dec, int mbc) {
this.in = null;
this.ch = ch;
this.decoder = dec;
this.cs = dec.charset();
this.bb = ByteBuffer.allocate(mbc < 0
? DEFAULT_BYTE_BUFFER_SIZE
: (mbc < MIN_BYTE_BUFFER_SIZE
? MIN_BYTE_BUFFER_SIZE
: mbc));
bb.flip();
}
专门构造InputStreamReader的构造方法
入参 输入流InputStream in 同步锁对象 Object lock 指定编码String charsetName
public static StreamDecoder forInputStreamReader(InputStream in,
Object lock,
String charsetName)
throws UnsupportedEncodingException
{
String csn = charsetName;
if (csn == null)
csn = Charset.defaultCharset().name();
try {
if (Charset.isSupported(csn))
return new StreamDecoder(in, lock, Charset.forName(csn));
} catch (IllegalCharsetNameException x) { }
throw new UnsupportedEncodingException (csn);
}
类似上面 不过是直接指定编码字符集对象Charset 少了一些判断
public static StreamDecoder forInputStreamReader(InputStream in,
Object lock,
Charset cs)
{
return new StreamDecoder(in, lock, cs);
}
public static StreamDecoder forInputStreamReader(InputStream in,
Object lock,
CharsetDecoder dec)
{
return new StreamDecoder(in, lock, dec);
}
读取方法 内部调用了私有read0()方法
public int read() throws IOException {
return read0();
}
@SuppressWarnings("fallthrough")
private int read0() throws IOException {
synchronized (lock) {
// 如果只有一个(左侧高位)字符 则直接返回 并haveLeftoverChar置位false
if (haveLeftoverChar) {
haveLeftoverChar = false;
return leftoverChar;
}
// 包装更多的字节数据
char cb[] = new char[2];
int n = read(cb, 0, 2);
switch (n) {
读取完毕 返回-1
case -1:
return -1;
case 2:
leftoverChar = cb[1];
haveLeftoverChar = true;
case 1:
return cb[0];
default:
assert false : n;
return -1;
}
}
}
默认字节缓冲大小
private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
多线程可见量 用于多线程下校验流是否关闭
private volatile boolean closed;
字符集
private Charset cs;
字符解码器 可以将一个字节序列按照特定的字符集转换成一个16位的Unicode序列
private CharsetDecoder decoder;
字节缓冲
private ByteBuffer bb;
输入流
private final OutputStream out;
写入信道
private WritableByteChannel ch;
是否保存左字符标志
为了保证读入的字符不乱码 则每次读入不能少于两个字符
如果只想要返回一个字符 那么可以保存左侧字符 等到下次返回
private boolean haveLeftoverChar = false;
private char leftoverChar;
private CharBuffer lcb = null;
其中比较重要的就是构造OutputStream的构造方法 和StreamDecoder大同小异
public static StreamEncoder forOutputStreamWriter(OutputStream out,
Object lock,
String charsetName)
throws UnsupportedEncodingException
{
String csn = charsetName;
if (csn == null)
csn = Charset.defaultCharset().name();
try {
if (Charset.isSupported(csn))
return new StreamEncoder(out, lock, Charset.forName(csn));
} catch (IllegalCharsetNameException x) { }
throw new UnsupportedEncodingException (csn);
}
public static StreamEncoder forOutputStreamWriter(OutputStream out,
Object lock,
Charset cs)
{
return new StreamEncoder(out, lock, cs);
}
public static StreamEncoder forOutputStreamWriter(OutputStream out,
Object lock,
CharsetEncoder enc)
{
return new StreamEncoder(out, lock, enc);
}
写入方法 虽然一系列复杂的判断 但最终是调用了writeBytes()方法写入的
public void write(int c) throws IOException {
char cbuf[] = new char[1];
cbuf[0] = (char) c;
write(cbuf, 0, 1);
}
public void write(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
implWrite(cbuf, off, len);
}
}
public void write(String str, int off, int len) throws IOException {
/* Check the len before creating a char buffer */
if (len < 0)
throw new IndexOutOfBoundsException();
char cbuf[] = new char[len];
str.getChars(off, off + len, cbuf, 0);
write(cbuf, 0, len);
}
public void write(CharBuffer cb) throws IOException {
int position = cb.position();
try {
synchronized (lock) {
ensureOpen();
implWrite(cb);
}
} finally {
cb.position(position);
}
}
write()方法调用implWrite()方法 implWrite()方法调用writeBytes()方法
void implWrite(char cbuf[], int off, int len)
throws IOException
{
CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
implWrite(cb);
}
内部一系列判断 就是判断流是否读完 最后剩余不够两个字节
(英文字符占一个字节 中文字符占两个字节)如何处理
void implWrite(CharBuffer cb)
throws IOException
{
if (haveLeftoverChar) {
flushLeftoverChar(cb, false);
}
while (cb.hasRemaining()) {
CoderResult cr = encoder.encode(cb, bb, false);
if (cr.isUnderflow()) {
assert (cb.remaining() <= 1) : cb.remaining();
if (cb.remaining() == 1) {
haveLeftoverChar = true;
leftoverChar = cb.get();
}
break;
}
if (cr.isOverflow()) {
assert bb.position() > 0;
writeBytes();
continue;
}
cr.throwException();
}
}
真正的写入方法writeBytes()
private void writeBytes() throws IOException {
bb.flip();
int lim = bb.limit();
int pos = bb.position();
此处使用了assert 断言关键字
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (rem > 0) {
if (ch != null) {
if (ch.write(bb) != rem)
assert false : rem;
} else {
out.write(bb.array(), bb.arrayOffset() + pos, rem);
}
}
bb.clear();
}