InputStreamReader和StreamDecoder的源码和使用方法详细分析(windows操作系统,JDK8)
InputStreamReader——FileInputStream和FileReader之间的桥梁计算机存储的单位是字节尽管我们平时在操作系统此处以windows操作系统为例中使用.txt文件保存了汉字这样的字符但是对计算机而言这些.txt文件中的汉字其实是以字节形式存在的所以当使用者使用字节输入流比如FileInputStream来读取文件的时候其实是一个一个的单字节读取1个字节可以表示28256个符号比如ASCII码就是用1个字节表示的编码方案 覆盖了英文中的所有符号但是不同字符集解码成字符的时候需要不同个数的字节比如中文1个字节就无法表示完所有的中文汉字因为中文汉字有5000个远远超过了28256覆盖的范围因此使用字节输入流比如FileInputStream来读取中文的时候会出现乱码InputStreamReader.class就可以把字节输入流读取的字节进行缓冲然后再通过字符集解码成字符返回。换句话说InputStreamReader.class是从字节流到字符流的桥接器InputStreamReader.class可以使用指定的字符集读取字节并将它们解码为字符。 它使用的字符集可以通过名称指定也可以明确指定或者可以接受操作系统平台的默认字符集。每次调用一个InputStreamReader的read()函数都可能导致从底层字节输入流中读取一个或多个字节。 为了实现字节到字符的有效转换InputStreamReader.class可以从底层字节输入流中提取比满足当前读取操作所需的更多字节。read()函数会尝试尽量从底层字节输入流中读取2个字符到字符缓冲区中注意这里是尽量比如遇到文件的最后字符时就只能读取到1个字符因此每次read()函数读取的字节数是不定的。InputStreamReader的UML关系图如下所示imageInputStreamReader.class的源码如下所示package java.io;import java.nio.charset.Charset;import java.nio.charset.CharsetDecoder;import sun.nio.cs.StreamDecoder;public class InputStreamReader extends Reader {private final StreamDecoder sd; //使用InputStream类型字节输入流对象构造InputStreamReader对象Reader.class::lock变量指向的对象锁也是这个InputStream类型字节输入流对象使用的字符集为UTF-8默认值 public InputStreamReader(InputStream in) { super(in);//设置Reader.class::lock变量指向的对象锁也是这个InputStream类型字节输入流对象 try { sd StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object } catch (UnsupportedEncodingException e) { //如果有异常抛出一个Error直接退出系统进程 // The default encoding should always be available throw new Error(e); } } //使用InputStream类型字节输入流对象构造InputStreamReader对象Reader.class::lock变量指向的对象锁也是这个InputStream类型字节输入流对象使用的字符集为自己用字符串指定的字符集 public InputStreamReader(InputStream in, String charsetName) throws UnsupportedEncodingException { super(in);//设置Reader.class::lock变量指向的对象锁也是这个InputStream类型字节输入流对象 if (charsetName null) throw new NullPointerException(charsetName); sd StreamDecoder.forInputStreamReader(in, this, charsetName); } //使用InputStream类型字节输入流对象构造InputStreamReader对象Reader.class::lock变量指向的对象锁也是这个InputStream类型字节输入流对象使用的字符集为自己用Charset对象指定的字符集 public InputStreamReader(InputStream in, Charset cs) { super(in);//设置Reader.class::lock变量指向的对象锁也是这个InputStream类型字节输入流对象 if (cs null) throw new NullPointerException(charset); sd StreamDecoder.forInputStreamReader(in, this, cs); } //使用InputStream类型字节输入流对象构造InputStreamReader对象Reader.class::lock变量指向的对象锁也是这个InputStream类型字节输入流对象使用的字符集为自己用CharsetDecoder对象指定的字符集 public InputStreamReader(InputStream in, CharsetDecoder dec) { super(in); if (dec null) throw new NullPointerException(charset decoder); sd StreamDecoder.forInputStreamReader(in, this, dec); } //调用StreamDecoder.class::getEncoding()函数获取StreamDecoder使用的解码方案字符集这些解码方案字符集包括但不限于ISO_8859_1US_ASCIIUnicode包括UTF-8... public String getEncoding() { return sd.getEncoding(); } //调用StreamDecoder.class::read()函数返回1个字符如果还有字符的话 public int read() throws IOException { return sd.read(); } //调用StreamDecoder.class::read(char cbuf[], int offset, int length) 函数一次性读取length个字符到字符数组char[] cbuf的[offset,offsetlength)索引位置并返回实际读取到的字符数量如果有足够多的字符的话 public int read(char cbuf[], int offset, int length) throws IOException { return sd.read(cbuf, offset, length); } //调用StreamDecoder.class::ready()函数 //如果字节缓冲区ByteBuffer bb不为空或者底层字节输入流或者文件中有字节可供读取则返回true public boolean ready() throws IOException { return sd.ready(); } //调用StreamDecoder.class::close()函数 public void close() throws IOException { sd.close(); }}二、StreamDecoder——InputStreamReader中实际将字节解码的JDK内部类InputStreamReader.class中的所有函数本质都是在调用StreamDecoder.class中的函数包括InputStreamReader的构造函数都是在调用StreamDecoder.class::forInputStreamReader()函数StreamDecoder的函数中会根据指定的字符集创造指定的字节解码器字节解码器的作用就是解码字节成字符。同时StreamDecoder.class这个类是在sun.nio.cs包下的类因此Oracle的官方JDK中无法看到相关的源码必须要通过OpenJDK来查看OpenJDK的github地址如下https://github.com/openjdk/jdk/blob/jdk-25%2B20/src/java.base/share/classes/sun/nio/cs/StreamEncoder.javaStreamDecoder的UML关系图如下所示imageStreamDecoder.class的源码如下所示package sun.nio.cs;import java.io.IOException;import java.io.InputStream;import java.io.UnsupportedEncodingException;import java.io.Reader;import java.nio.ByteBuffer;import java.nio.CharBuffer;import java.nio.channels.FileChannel;import java.nio.channels.ReadableByteChannel;import java.nio.charset.Charset;import java.nio.charset.CharsetDecoder;import java.nio.charset.CoderResult;import java.nio.charset.CodingErrorAction;import java.nio.charset.IllegalCharsetNameException;import java.nio.charset.UnsupportedCharsetException;import java.util.Arrays;public class StreamDecoder extends Reader {// ByteBuffer字节缓冲区 bb的大小分为以下2中情况//场景一当使用Channels对象创建自己这个StreamDecoder对象的时候会对实参中的minBufferCapmbc变量做判断又分为以下3种情况// a、当minBufferCapmbc0时会为ByteBuffer字节缓冲区 bb分配一个8192DEFAULT_BYTE_BUFFER_SIZE的长度// b、当0minBufferCapmbc32时会为ByteBuffer字节缓冲区 bb分配一个32MIN_BYTE_BUFFER_SIZE的长度// c、当32minBufferCapmbc时为ByteBuffer字节缓冲区 bb分配的长度就是minBufferCapmbc这个实参//场景二当使用InputStreamReader 对象创建自己这个StreamDecoder对象的时候没有指定ByteBuffer字节缓冲区长度的实参会直接为ByteBuffer字节缓冲区 bb分配一个8192DEFAULT_BYTE_BUFFER_SIZE的长度private static final int MIN_BYTE_BUFFER_SIZE 32;private static final int DEFAULT_BYTE_BUFFER_SIZE 8192;//标记这个StreamDecoder是否开启false表示开启可以使用StreamDecoder.class::read()函数和StreamDecoder.class::ready()函数了true表示关闭不能再使用StreamDecoder.class::read()函数和StreamDecoder.class::ready()函数了private volatile boolean closed;//如果StreamDecoder已经被boolean closed变量标记为关闭true了则不能再执行StreamDecoder.class::read()函数和StreamDecoder.class::ready()函数了否则会抛出一个IOException(“Stream closed”)private void ensureOpen() throws IOException {if (closed)throw new IOException(“Stream closed”);}// In order to handle surrogates properly we must never try to produce // fewer than two characters at a time. If were only asked to return one // character then the other is saved here to be returned later. // // 特别为read()函数准备可以更好的处理代理字符read()函数只要求返回1个字符但是实际上是读取了2个字符多读取的1个字符会被赋值缓存到char leftoverChar变量中而boolean haveLeftoverChar变量只是用来标记char leftoverChar中是否有缓存的字符 //这个思路类似于BufferedReader中用boolean skipLF跳过换行符\r\n在windows操作系统下面但是又并不完全相同BufferedReader中的所有数据都保存在char[] cb缓冲区数组中 private boolean haveLeftoverChar false; private char leftoverChar; //提供给InputStreamReader对象创建自己这个StreamDecoder对象的工厂函数 public static StreamDecoder forInputStreamReader(InputStream in, Object lock, String charsetName) throws UnsupportedEncodingException { try { return new StreamDecoder(in, lock, Charset.forName(charsetName)); } catch (IllegalCharsetNameException | UnsupportedCharsetException x) { throw new UnsupportedEncodingException (charsetName); } } //提供给InputStreamReader对象创建自己这个StreamDecoder对象的工厂函数 public static StreamDecoder forInputStreamReader(InputStream in, Object lock, Charset cs) { return new StreamDecoder(in, lock, cs); } //提供给InputStreamReader对象创建自己这个StreamDecoder对象的工厂函数 public static StreamDecoder forInputStreamReader(InputStream in, Object lock, CharsetDecoder dec) { return new StreamDecoder(in, lock, dec); } // Factory for java.nio.channels.Channels.newReader //提供给Channels对象创建自己这个StreamDecoder对象的工厂函数 public static StreamDecoder forDecoder(ReadableByteChannel ch, CharsetDecoder dec, int minBufferCap) { return new StreamDecoder(ch, dec, minBufferCap); } //接下来的函数都是与 InputStreamReader 中的函数一一对应的换句话说接下来的函数都是通过 InputStreamReader 中对应功能的函数可能名称不对应但是功能一定对应来调用的 //所有同步和状态/参数检查都在这些函数中完成下面定义的具体流解码器子类无需进行此类检查。 // -- Public methods corresponding to those in InputStreamReader -- // All synchronization and state/argument checking is done in these public // methods; the concrete stream-decoder subclasses defined below need not // do any such checking. //返回这个StreamDecoder使用的解码方案字符集这些解码方案字符集包括但不限于ISO_8859_1US_ASCIIUnicode包括UTF-8... public String getEncoding() { if (isOpen()) return encodingName(); return null; } //对应InputStreamReader.class::read()函数返回单个字符实际调用下面的read0()函数 public int read() throws IOException { return read0(); } //SuppressWarnings(fallthrough)可以压制即消除编译器对 case 2 分支缺少中断语句(return、break、throw...等)的报警 //函数内部做了线程同步如果有足够字符的话一次性从read(char[] cbuf, int offset, int length)函数中读取2个字符 SuppressWarnings(fallthrough) private int read0() throws IOException { synchronized (lock) { if (haveLeftoverChar) { //如果char leftoverChar变量中有被赋值缓存的字符char则直接返回并把boolean haveLeftoverChar标记改为false表示char leftoverChar变量中有被赋值缓存的字符已经被读取了 haveLeftoverChar false; return leftoverChar; } // 创建了长度为2的字符数组char[] cb char[] cb new char[2]; // 调用下面的read(char[] cbuf, int offset, int length)函数返回读取的字符个数 int n read(cb, 0, 2); switch (n) { case -1:// 若read(char[] cbuf, int offset, int length)函数返回-1代表读到末尾 return -1; //直接返回-1 case 2:// 若read(char[] cbuf, int offset, int length)函数返回2代表读到了2个字符 leftoverChar cb[1];//将char[] cb数组中第2个字符索引1位置放到char leftoverChar变量中暂时缓存 haveLeftoverChar true;//并把boolean haveLeftoverChar标记改为true表示char leftoverChar变量中有被赋值缓存的字符 // FALL THROUGH case 1:// 若read(char[] cbuf, int offset, int length)函数返回1代表读到了1个字符 return cb[0];//实际执行的时候如果read(char[] cbuf, int offset, int length)函数的返回值为2char[] cb数组中有2个字符这里会被case2穿透直接返回char[] cb数组中的第1个字符索引0位置 //详情看小标题x.x.x default: assert false : n;//实际编译后这里会被编译器优化到case2之前详情看小标题x.x.x return -1; } } } //如果有足够多字符的话一次性读取length个字符到字符数组char[] cbuf的[offset,offsetlength)索引位置并返回实际读取到的字符数量 //函数内部做了线程同步 public int read(char[] cbuf, int offset, int length) throws IOException { synchronized (lock) { //先在这个函数的调用栈上复制offset为off、length为len int off offset; int len length; ensureOpen(); if ((off 0) || (off cbuf.length) || (len 0) || ((off len) cbuf.length) || ((off len) 0)) {//char[]数组cbuf的[offset,offsetlength)左闭右开索引位置是否有越界的检查 throw new IndexOutOfBoundsException();//越界的话抛出一个IndexOutOfBoundsException } if (len 0) return 0;//如果len0结束本次函数调用并返回0 //实际读取到的字符数量 int n 0; // 如果char leftoverChar变量中有被赋值缓存的字符char则boolean haveLeftoverChar变量为true if (haveLeftoverChar) { // 将char leftoverChar变量中被赋值缓存的字符char先放到字符数组char[] cbuf的offoffset索引位置 cbuf[off] leftoverChar; off; len--;//然后将offoffset索引位置右移1位将len-1 haveLeftoverChar false;//并把boolean haveLeftoverChar标记改为false表示char leftoverChar变量中有被赋值缓存的字符了 n 1;//设置n1 if ((len 0) || !implReady()) // Return now if this is all we can produce w/o blocking //如果这已经是StreamDecoder在不阻塞的情况下能读取到字符数组char[] cbuf中的全部字符了就立即返回。 return n; } if (len 1) { //如果此时len1则表示接下来只需要向字符数组char[] cbuf中再读取1个字符那么直接调用read0()函数即可此时len1分为以下2中情况 //场景一要读取到字符数组char[] cbuf中的字符数量length2同时char leftoverChar变量中被赋值缓存了1个字符 //场景二要读取到字符数组char[] cbuf中的字符数量length1并且char leftoverChar变量中没有缓存字符 int c read0();//read0()函数一次只能读取1个字符 if (c -1)//c-1表示从已经从read0()函数中读取不到任何字符了 return (n 0) ? -1 : n;//返回实际读取到字符数组char[] cbuf中的字符数量n或者-1 cbuf[off] (char)c;//如果还能从read0()函数中读取到字符则将读取到的这个字符保存到字符数组char[] cbuf中 return n 1;//累加1到实际读取到字符数组char[] cbuf中的字符数量n上面并返回 } // Read remaining characters //执行完上面的代码到这里的时候off和len有以下2种可能 //可能1char leftoverChar变量中没有被赋值缓存的字符char并且len1要填充到字符数组char[] cbuf中的字符数量length1 //可能2char leftoverChar变量中有被赋值缓存的字符char并且len2要填充到字符数组char[] cbuf中的字符数量length2。 //那么执行implRead()函数向字符数组char[] cbuf的[off,off len)索引位置填充len个字符并返回实际填充的字符数量nr举例如下 //假如当int offset的实参为0int length的实参为5时如果之前满足可能1则此时[off,off len)为[0,5)返回5或者-1如果之前满足可能2则此时[off,off len)为[1,5)返回4或者-1 int nr implRead(cbuf, off, off len); // At this point, n is either 1 if a leftover character was read, // or 0 if no leftover character was read. If n is 1 and nr is -1, // indicating EOF, then we dont return their sum as this loses data. //此时nr有以下2种可能得值-1或者实际向字符数组char[] cbuf的[off,off len)索引位置填充的字符数量nr当nr-1时n只有以下2种可能 //可能a之前char leftoverChar变量中没有被赋值缓存的字符charn0此时return -1 //可能b之前char leftoverChar变量中有被赋值缓存的字符charn1此时return 1 //当nr≠-1时表示的确向字符数组char[] cbuf的[off,off len)索引位置填充了nr个字符return nnr0nr或者1nr return (nr 0) ? (n 1 ? 1 : nr) : (n nr); } } public boolean ready() throws IOException { synchronized (lock) { ensureOpen(); return haveLeftoverChar || implReady(); } } //关闭这个StreamDecoder中成员变量引用的字节输入流和其它资源并且设置boolean closedtrue标记这个StreamDecoder已经关闭了不能再使用所有重载的StreamDecoder.class::read()函数和StreamDecoder.class::ready()函数了 //函数内部做了线程同步 public void close() throws IOException { synchronized (lock) { if (closed) return; try { implClose(); } finally { closed true; } } } private boolean isOpen() { return !closed; } public void fillZeroToPosition() throws IOException { synchronized (lock) { Arrays.fill(bb.array(), bb.arrayOffset(), bb.arrayOffset() bb.position(), (byte)0); } } // -- Charset-based stream decoder impl -- private final Charset cs; //StreamDecoder中真正用来将ByteBuffer bb中的字节解码成不同字符的工具ByteBuffer bb中的字节也是从字节输入流InputStream in中获取到的可以解码的字符集解码方案包括但不限于ISO_8859_1US_ASCIIUnicode包括UTF-8... private final CharsetDecoder decoder; //字节输入流InputStream in中的字节被CharsetDecoder decoder解码成字符之前会先读取到ByteBuffer bb中缓存 private final ByteBuffer bb; //字节输入流真正通过StreamDecoder转换成字符的字节都是从这个字节输入流中获取的不过将这些字节转换成字符之前会先读取到ByteBuffer bb中缓存 private final InputStream in; //只有通过Channels对象创建自己这个StreamDecoder对象的时候才会使用到这个变量通InputStreamReader对象创建自己这个StreamDecoder对象的时候用不到这个变量 private final ReadableByteChannel ch; //构造函数 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) { //设置Reader.class::lock变量指向的对象锁是创建自己这个StreamDecoder对象的InputStreamReader对象 super(lock); this.cs dec.charset(); this.decoder dec; this.in in; this.ch null; this.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(); } //最终调用的方法利用底层字节输入流读取字节到字节缓冲区ByteBuffer bb中 private int readBytes() throws IOException { bb.compact(); try { if (ch ! null) { // Read from the channel int n ch.read(bb); if (n 0) return n; } else { // Read from the input stream, and then update the buffer // 从输入流中读取然后更新字节缓冲区ByteBuffer bb int lim bb.limit(); int pos bb.position(); assert (pos lim); int rem (pos lim ? lim - pos : 0); int n in.read(bb.array(), bb.arrayOffset() pos, rem); if (n 0) return n; if (n 0) throw new IOException(Underlying input stream returned zero bytes); assert (n rem) : n n , rem rem; bb.position(pos n); } } finally { //即使抛出 IOException 也要翻转否则字节缓冲区ByteBuffer会有问题 bb.flip();//反转字节缓冲区中的所有字节 } //计算读取到字节缓冲区ByteBuffer中的字节数量并返回 int rem bb.remaining(); assert (rem ! 0) : rem; return rem; } //如果有足够多字符的话一次性填充end-off个字符到字符数组char[] cbuf的[off,end)索引位置并返回实际填充到字符数组char[] cbuf中的字符数量 int implRead(char[] cbuf, int off, int end) throws IOException { //要填充到字符数组char[] cbuf中的字符数量必须1 assert (end - off 1); //将字符数组char[] cbuf包装到字符缓冲区CharBuffer cb中包装到缓冲区之后字符缓冲区CharBuffer cb中修改的内容会同步修改到字符数组char[] cbuf中 //可以将CharBuffer对象理解为一个数组管理的索引位置为[off,end)区间的end - off个字符 CharBuffer cb CharBuffer.wrap(cbuf, off, end - off); if (cb.position() ! 0) { //将字符缓冲区CharBuffer cb管理的索引区间和要填充的字符数组char[] cbuf进行对齐确保cb[0]cbuf[0ff] cb cb.slice(); } //eoffalse表示还没有读到字节输入流或者叫文件的末尾字节输入流或者文件中还有字节可以提供给StreamDecoder来进行转换 boolean eof false; for (;;) { //从字节缓冲区ByteBuffer bb中解码尽可能多的字节将结果写入给定的字符缓冲区CharBuffer cb中也就是字符数组char[] cbuf中用于读取字符 CoderResult cr decoder.decode(bb, cb, eof); if (cr.isUnderflow()) { if (eof) break; if (!cb.hasRemaining()) //检查字符缓冲区CharBuffer cb中是否还有剩余空间若没有则跳出循环 break; if ((cb.position() 0) !inReady()) //字符缓冲区CharBuffer cb存在内容时跳出循环以便返回读取到的字符数 break; // Block at most once //读取字节到字节缓冲区ByteBuffer bb int n readBytes(); if (n 0) { //若读取字节到字节缓冲区ByteBuffer bb时返回了-1代表着已到了字节输入流或者叫文件的末尾则不会有其它字节输入了 eof true; if ((cb.position() 0) (!bb.hasRemaining())) //如果此时字符缓冲区CharBuffer cb和字节缓冲区ByteBuffer bb中都没有数据了则跳出循环 break; } continue; } if (cr.isOverflow()) { //当从字节缓冲区ByteBuffer bb中解码的字节太多导致溢出时字符缓冲区CharBuffer cb必须有解码的字符否则这里的断言就会报错 assert cb.position() 0; //溢出时也跳出循环 break; } cr.throwException(); } if (eof) { // ## Need to flush decoder //如果已经到了字节输入流或者叫文件的末尾则重置解码器清除所有内部状态。 decoder.reset(); } if (cb.position() 0) { if (eof) { return -1; } assert false; } //返回字符缓冲区CharBuffer cb的当前位置代表着实际读取或者填充到字符数组char[] cbuf中的字符数量 return cb.position(); } String encodingName() { return ((cs instanceof HistoricallyNamedCharset) ? ((HistoricallyNamedCharset)cs).historicalName() : cs.name()); } private boolean inReady() { try { return (((in ! null) (in.available() 0)) || (ch instanceof FileChannel)); // ## RBC.available()? } catch (IOException x) { return false; } } boolean implReady() { return bb.hasRemaining() || inReady(); } void implClose() throws IOException { if (ch ! null) { ch.close(); } else { in.close(); } }}2.1、构造函数和可以解码的字符集解码方案StreamDecoder的构造函数从来不单独对外提供所以使用者包括JDK的内部类必须要同过forInputStreamReader()、forDecoder()这些工厂函数来创建StreamDecoder.class的实例。同时StreamDecoder.class在sun.nio.cs包中使用者无法引入这个包所以使用者也无法在自己的代码中创建StreamDecoder的实例因此StreamDecoder.class中提供的工厂函数forInputStreamReader()、forDecoder()只是提供给JDK的内部类InputStreamReader.class和Channels.class来使用的。如下所示public class StreamDecoder extends Reader {…省略部分代码…//提供给InputStreamReader对象创建自己这个StreamDecoder对象的工厂函数public static StreamDecoder forInputStreamReader(InputStream in,Object lock,String charsetName)throws UnsupportedEncodingException{try {return new StreamDecoder(in, lock, Charset.forName(charsetName));} catch (IllegalCharsetNameException | UnsupportedCharsetException x) {throw new UnsupportedEncodingException (charsetName);}}//提供给InputStreamReader对象创建自己这个StreamDecoder对象的工厂函数 public static StreamDecoder forInputStreamReader(InputStream in, Object lock, Charset cs) { return new StreamDecoder(in, lock, cs); } //提供给InputStreamReader对象创建自己这个StreamDecoder对象的工厂函数 public static StreamDecoder forInputStreamReader(InputStream in, Object lock, CharsetDecoder dec) { return new StreamDecoder(in, lock, dec); } // Factory for java.nio.channels.Channels.newReader //提供给Channels对象创建自己这个StreamDecoder对象的工厂函数 public static StreamDecoder forDecoder(ReadableByteChannel ch, CharsetDecoder dec, int minBufferCap) { return new StreamDecoder(ch, dec, minBufferCap); } //构造函数 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) { //设置Reader.class::lock变量指向的对象锁是创建自己这个StreamDecoder对象的InputStreamReader对象 super(lock); this.cs dec.charset(); this.decoder dec; this.in in; this.ch null; this.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(); } ...省略部分代码...}2.1.1、可以解码的字符集解码方案StreamDecoder.class中将字节输入流中的字节解码成字符的过程是通过CharsetDecoder decoder这个工具来完成的如下所示...省略部分代码... //StreamDecoder中真正用来将ByteBuffer bb中的字节解码成不同字符的工具ByteBuffer bb中的字节也是从字节输入流InputStream in中获取到的可以解码的字符集解码方案包括但不限于ISO_8859_1US_ASCIIUnicode包括UTF-8... private final CharsetDecoder decoder; //如果有足够多字符的话一次性填充end-off个字符到字符数组char[] cbuf的[off,end)索引位置并返回实际填充到字符数组char[] cbuf中的字符数量 int implRead(char[] cbuf, int off, int end) throws IOException { ...省略部分代码... //eoffalse表示还没有读到字节输入流或者叫文件的末尾字节输入流或者文件中还有字节可以提供给StreamDecoder来进行转换 boolean eof false; for (;;) { //从字节缓冲区ByteBuffer bb中解码尽可能多的字节将结果写入给定的字符缓冲区CharBuffer cb中也就是字符数组char[] cbuf中用于读取字符 CoderResult cr decoder.decode(bb, cb, eof); ...省略部分代码...StreamDecoder.class可以解码的字符集解码方案包括但不限于ISO_8859_1US_ASCIIUnicode包括UTF-8…

相关新闻

文书妙笔Ai公文写作实操:用“写作灵感“功能突破写材料卡壳

文书妙笔Ai公文写作实操:用“写作灵感“功能突破写材料卡壳

本文记录我用文书妙笔ai公文写作「文思泉涌」处理「写材料卡壳,对着空白文档半天写不出下句」的完整过程,面向愿意用工具提效、不想在重复劳动上走弯路的职场人,只讲操作,不谈虚的。高发卡点过渡句是我的最高频卡点。现在写长材料…

2026/7/22 11:15:02 阅读更多 →
性价比高的IVD人源质控抗体哪里找

性价比高的IVD人源质控抗体哪里找

在体外诊断(IVD)领域,人源质控抗体的质量和性价比直接影响着诊断结果的准确性和企业的成本效益。然而,当前市场上进口抗体价格高昂、供货不稳定等问题,让不少 IVD 企业头疼不已。今天,就为大家介绍一家能提…

2026/7/25 7:52:26 阅读更多 →
深入解析嵌入式以太网控制器驱动:描述符队列与中断机制实战

深入解析嵌入式以太网控制器驱动:描述符队列与中断机制实战

1. 项目概述:从硬件描述符到稳定驱动 在嵌入式网络开发里,调通一个以太网控制器(EMAC)的驱动,远不止是调用几个初始化API那么简单。真正的挑战,往往藏在数据手册那些关于描述符队列、中断同步和所有权标志位…

2026/7/22 11:15:02 阅读更多 →

最新新闻

Win11Debloat终极指南:5分钟让你的Windows 11重获新生

Win11Debloat终极指南:5分钟让你的Windows 11重获新生

Win11Debloat终极指南:5分钟让你的Windows 11重获新生 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes to declutter and cu…

2026/7/25 21:14:07 阅读更多 →
C++算术平均值计算优化:从C++98到C++23的性能演进与实战指南

C++算术平均值计算优化:从C++98到C++23的性能演进与实战指南

1. 项目概述:为什么我们要重新审视算术平均值?算术平均值,这个在数学课本里就出现过的概念,对程序员来说简直是家常便饭。从计算学生平均分到分析系统性能指标,(a b ... n) / count这个公式我们闭着眼睛都能写出来。…

2026/7/25 21:14:07 阅读更多 →
抖音无水印下载终极指南:从新手到高手的完整教程

抖音无水印下载终极指南:从新手到高手的完整教程

抖音无水印下载终极指南:从新手到高手的完整教程 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support. 抖…

2026/7/25 21:14:07 阅读更多 →
如何永久保存微信聊天记录:WeChatMsg完整数据管理指南

如何永久保存微信聊天记录:WeChatMsg完整数据管理指南

如何永久保存微信聊天记录:WeChatMsg完整数据管理指南 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/WeCha…

2026/7/25 21:14:07 阅读更多 →
如何快速掌握RSEM:RNA-Seq定量分析的终极实用指南

如何快速掌握RSEM:RNA-Seq定量分析的终极实用指南

如何快速掌握RSEM:RNA-Seq定量分析的终极实用指南 【免费下载链接】RSEM RSEM: accurate quantification of gene and isoform expression from RNA-Seq data 项目地址: https://gitcode.com/gh_mirrors/rs/RSEM 你是否正在为RNA-Seq数据的基因表达定量分析而…

2026/7/25 21:14:07 阅读更多 →
如何实现微信聊天记录的终极备份?三步完成完整导出与智能数据分析

如何实现微信聊天记录的终极备份?三步完成完整导出与智能数据分析

如何实现微信聊天记录的终极备份?三步完成完整导出与智能数据分析 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trendi…

2026/7/25 21:13:07 阅读更多 →

日新闻

突破文档下载限制:kill-doc让你看到的都能保存

突破文档下载限制:kill-doc让你看到的都能保存

突破文档下载限制:kill-doc让你看到的都能保存 【免费下载链接】kill-doc 看到经常有小伙伴们需要下载一些免费文档,但是相关网站浏览体验不好各种广告,各种登录验证,需要很多步骤才能下载文档,该脚本就是为了解决您的…

2026/7/25 0:00:35 阅读更多 →
C++ string类模拟实现:从深拷贝到内存管理的完整指南

C++ string类模拟实现:从深拷贝到内存管理的完整指南

1. 项目概述:为什么我们要“手撕”string类?在C的学习道路上,尤其是从C语言过渡到C的“初阶”阶段,string类绝对是一个绕不开的核心。标准库里的std::string用起来太方便了,、find、substr,几个操作符和函数…

2026/7/25 0:00:35 阅读更多 →
三角洲寻宝鼠工具:高效文件搜索与资源管理实战指南

三角洲寻宝鼠工具:高效文件搜索与资源管理实战指南

1. 先搞清楚“三角洲寻宝鼠”到底是什么工具从名称来看,“三角洲寻宝鼠”更像是一个资源查找或文件检索类工具,而不是游戏或娱乐软件。这类工具的核心价值在于帮助用户快速定位特定资源,比如文档、图片、压缩包或特定格式的文件。如果你经常需…

2026/7/25 0:00:35 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/25 5:08:22 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/25 5:13:53 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/24 18:52:18 阅读更多 →

月新闻