JAVA基础知识之StreamDecoder流

一、StreamDecoder流源码

     个人整理的StreamDecoder流源码,其中的注释都为个人理解,非官方注释,下载地址

二、StreamDecoder流实例域

      需要知晓实例域的变量代表着什么意思

    // 默认字节缓冲区大小,最小是32,最大是8192个字节
    private static final int MIN_BYTE_BUFFER_SIZE = 32;
    private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;

    // 确保流是被打开状态否则不能输入输出
    private volatile boolean isOpen = true;

    private void ensureOpen() throws IOException
    {
        if (!isOpen)
            throw new IOException("Stream closed");
    }

    // 特别为read准备,read方法要求返回一个字符,但是实际是读取2个字符,剩余1个字符会被赋予给leftoverChar变量,而haveLeftoverChar用于判断
    private boolean haveLeftoverChar = false;
    private char leftoverChar;

    // 字符集名称
    private Charset cs;

    // 解码器
    private CharsetDecoder decoder;

    // 字节缓冲对象
    private ByteBuffer bb;

    // 底层字节输入流
    private InputStream in;

    // 信道--暂未理解作用
    private ReadableByteChannel ch;    // 默认字节缓冲区大小,最小是32,最大是8192个字节
    private static final int MIN_BYTE_BUFFER_SIZE = 32;
    private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;

    // 确保流是被打开状态否则不能输入输出
    private volatile boolean isOpen = true;

    private void ensureOpen() throws IOException
    {
        if (!isOpen)
            throw new IOException("Stream closed");
    }

    // 特别为read准备,read方法要求返回一个字符,但是实际是读取2个字符,剩余1个字符会被赋予给leftoverChar变量,而haveLeftoverChar用于判断
    private boolean haveLeftoverChar = false;
    private char leftoverChar;

    // 字符集名称
    private Charset cs;

    // 解码器
    private CharsetDecoder decoder;

    // 字节缓冲对象
    private ByteBuffer bb;

    // 底层字节输入流
    private InputStream in;

    // 信道--暂未理解作用
    private ReadableByteChannel ch;

  三、StreamDecoder流构造方法

         通过构造方法对对象进行初始化赋值,特别注意给字节缓冲区设置了默认大小值,即8192

// 构造函数,本质是调用下面的方法
    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;

        // 此路径禁用,直到直接缓冲区更快?未理解
        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(); // 清空字符缓冲对象,保证最开始是空的
    }

四、InputStreamReader类构造方法与StreamDecoder类的关系

       根据之前InputStreamReader类的介绍,我们知晓了InputStreamReader的构造方法都是在调用StreamDecoder类的forInputStreamReader方法,因此我们仔细看下forInputStreamReader方法

         可以看出该方法的本质就是检验字符集名称是否存在以及被系统支持,然后调用构造方法构造StreamDecoder对象赋予给InputStreamReader类构造方法中的对象变量

// 根据字符集名称得到字符集对象并构造StreamDecoder对象
    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);
    }

五、StreamDecoder类的API

1)read()方法:返回读取的一个字符,当读到文件末尾时,返回数据字典-1

    // 读取方法,返回单个字符,实际调用下面的read0()方法
    public int read() throws IOException
    {
        return read0();
    }

    // 返回单个字符
    private int read0() throws IOException
    {
        synchronized (lock)
        {

            // haveLeftoverChar默认fasle,实例域有设置,因此最开始不会进入
            if (haveLeftoverChar)
            {
                haveLeftoverChar = false;
                return leftoverChar;
            }

            // 创建了长度为2的字符数组
            char cb[] = new char[2];

        // 调用下面read(char cbuf[], int offset, int length)方法,返回读取的字符个数

            int n = read(cb, 0, 2); 

            switch (n)
            {

         // 若返回-1.代表读到末尾
            case -1: 
                return -1;


        // 读取到2个字符,但是方法要求返回一个字符,因此第二个字符就赋予变量暂时缓存
            case 2: 

         // 第二个字符赋予变量leftoverChar 
                leftoverChar = cb[1];

         //修改条件为真  
         haveLeftoverChar = true; 

        // 代表读取到1个字符,直接返回数组的第一个元素即可
            case 1: 

                return cb[0];

            default:

                assert false : n;

                return -1;
            }
        }
    }

实际流程图:

2)read(char cbuf[], int offset, int length)方法:最多读取length个字节放入字符数组中,从字符数组的偏移量offset开始存储,返回实际读取存储的字节数,当读取到文件末尾时,返回-1。

// 读取最多length个字符到字符数组cbuf中,cbuf从offset下标开始存储,返回实际读取的字符数
// 当读取到文件末尾时,返回-1
    public int read(char cbuf[], int offset, int length) throws IOException
    {
        int off = offset;
        int len = length;
        synchronized (lock)
        {
            ensureOpen();
            if ((off < 0) || (off > cbuf.length) || (len < 0)
                    || ((off + len) > cbuf.length) || ((off + len) < 0))
            {
                throw new IndexOutOfBoundsException();
            }
            if (len == 0)
                return 0;

            int n = 0;


           // 如果上次read()方法中剩余1个字符还未被取出
            if (haveLeftoverChar)
            { 

               // 那么剩余的这个字符直接赋予到数组中
                cbuf[off] = leftoverChar; 
                off++;
                len--; 
                haveLeftoverChar = false; 
                n = 1;

                //判断len的值
                if ((len == 0) || !implReady()) {
                    return n;
                }        
            }

            //若此时len为1,则直接调用read()方法即可
            if (len == 1)
            { 

                int c = read0();
                if (c == -1){
                  return (n == 0) ? -1 : n;
                }
                   
                cbuf[off] = (char) c;
                return n + 1;
            }
            //若前面都未返回,则调用下面方法
            return n + implRead(cbuf, off, off + len);
        }
    }

实际流程图:

3) implRead(cbuf, off, end)方法:读取字符到数组中,从数组的偏移量offset开始存储,最多存储到偏移量end,返回实际读取存储的字符个数

//从下标off开始,到下标end,共end-off个字符到cbuf数组中,注意这里的end不是个数,而是代表了下标的意思
    int implRead(char[] cbuf, int off, int end) throws IOException
    {

        assert (end - off > 1);  

//将字符数组包装到缓冲区中,缓冲区修改,字符数组也会被修改

//cb本质理解为一个数组,当前位置为off,界限为end-off

        CharBuffer cb = CharBuffer.wrap(cbuf, off, end - off); 
        if (cb.position() != 0)
            cb = cb.slice();   //确保cb[0]=cbuf[0ff]

        boolean eof = false;  //代表着还有其他的字节输入提供
        for (;;)
        {

            //将字节缓冲区bb中解码尽可能多的字节,将结果写入给定的字符缓冲区cb中
            CoderResult cr = decoder.decode(bb, cb, eof);  

            if (cr.isUnderflow())        // 代表字节缓冲区内容已全部解码放入到字符缓冲区中
            {
                if (eof)
                    break;
                if (!cb.hasRemaining())  //cb.hasRemaining()代表是否还有空间剩余,若没有则跳出循环
                    break;

                //字符缓冲区存在内容时,则跳出循环,以便返回读取到的字符数
                if ((cb.position() > 0) && !inReady()) 

                    break; 

                int n = readBytes();  //读取字节到字节缓冲区bb中

                if (n < 0)    //若为-1,代表着已到末尾,则不会有其它输入了
                {
                    eof = true;

                    //字符缓冲区没有字符且字节缓冲区没有元素了,返回
                    if ((cb.position() == 0) && (!bb.hasRemaining())) 
                        break;
                    decoder.reset();  //重置解码器.清除所有内部状态。 
                }
                continue;     //代表着刚才的读取有内容。继续进行解码转换
            }

 //代表着字符缓冲区已满,应该使用未满的字符缓冲区在次调用该方法
            if (cr.isOverflow())       
            {
                assert cb.position() > 0;
                break;
            }
            cr.throwException();
        }

        if (eof)  //代表接下来不会有字节输入了
        {
           
             //重置解码器,清除状态
            decoder.reset();  
        }

        if (cb.position() == 0)
        {
            if (eof)
                return -1;
            assert false;
        }

        //返回字符缓冲区的当前位置,代表着实际读取存储的字符个数
        return cb.position(); 
    }

实际流程图:

4) readBytes():利用字节输入流尝试读取最多8192个字节到字节缓冲区中,此方法是核心点:读取字节到字节缓冲区才可以利用编码器编码字节成字符

   //最终调用的方法,利用底层字节输入流,读取字节到字节缓冲区中
    private int readBytes() throws IOException
    {
        bb.compact(); //压缩缓冲区,当缓冲区中当前位置已到界限时,则时当前位置归0,界限位置到容量位置
        try
        {
            if (ch != null)
            {
               
                int n = ch.read(bb);
                if (n < 0)
                    return n;
            }
            else
            {
                // 从输入流中读取,然后更新缓冲区
                int lim = bb.limit();
                int pos = bb.position();
                assert (pos <= lim);
                int rem = (pos <= lim ? lim - pos : 0);
                assert rem > 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
        {
            
            bb.flip();   //反转缓冲区,即limit位置位于position.position归于0
        }

        int rem = bb.remaining(); //读取到的字节数
        assert (rem != 0) : rem;
        return rem;
    }

5)close()方法:关闭资源,释放链接

 /**
     * 关闭资源
     */
    public void close() throws IOException
    {
        synchronized (lock)
        {
            if (!isOpen)
                return;
            implClose();
            isOpen = false;
        }
    }

    void implClose() throws IOException
    {
        if (ch != null)
            ch.close();
        else
            in.close();
    }

  六、InputStreamReader类和StreamDecoder类的联系

    1、InputStreamReader类中提到:使用指定的字符集读取字节并将它们解码为字符?

          InputStreamReader类构造函数本质是调用StreamDecoder类的方法,StreamDecoder的方法中会根据指定的字符集创造指定的字节解码器,字节解码器的作用就是解码字节成字符

    2、InputStreamReader类中提到:为了实现字节到字符的有效转换,可以从基础流中提取比满足当前读取操作所需的更多字节,请考虑在BufferedReader中包装InputStreamReader?

        StreamDecoder中一次读取的字节最多是8192个字节,因此假设使用InputStreamReader类读取1w个字符到数组中,那么就会很明显的要多次从基础流中读取字节,而根据API说明,BufferedReader类可以从基础流读取字节超过8192个的方法,以此来提高了读取的效率。因此需要查看BufferedReader的源码才可以了解

  

 

 

  • 11
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值