• JavaIO进阶系列——Writer类、StringWriter类


    Writer类

    Writer是一个用于写入字符流的抽象类,子类必须实现write(char[],int,int)、flush()和close()三个方法,我们可以通过覆盖其中的方法以提供更加强大的功能

    属性

    writeBuffer临时缓冲区

    临时缓冲区用于保存写入的字符串和单个字符,子类无法继承

    private char[] writeBuffer;
    
    • 1

    临时缓冲区的大小

    临时缓冲区的大小必须要大于等于1,而在Writer中默认定义为1024,而且可以看出这是不能修改的子类也无法继承

    private static final int WRITE_BUFFER_SIZE = 1024;
    
    • 1

    lock

    用于同步此流上的操作的对象。为了提高效率,字符流对象可以使用除自身以外的对象来保护关键部分。因此,子类应该使用这个字段中的对象,而不是这个或同步方法

    protected Object lock;
    
    • 1

    看完上面官方写的注释你是不是感觉压根没看懂?没错,确实但看注释根本不能知道这个lock属性到底用来干什么,接下来我们继续看下去,如果你感到没有耐心的话可以直接看下面我写的:构造方法

    Writer的无参构造

    从构造方法中可以看出,该类将自己当前的实例直接赋给了lock属性,所以显而易见,lock就是当前实例

    protected Writer() {
            this.lock = this;
        }
    
    • 1
    • 2
    • 3

    Writer的构造方法

    创建一个新的字符流编写器,其关键部分将在给定对象上同步
    这个构造方法是将当前Writer类中的属性lock赋为传入的对象

        protected Writer(Object lock) {
            if (lock == null) {
                throw new NullPointerException();
            }
            this.lock = lock;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    方法

    写入部分或全部字符串或字符数组:write方法

    写入单个字符
    1. 使用sychronized关键字锁住当前实例(this.lock)
    2. 判断当前的临时缓冲区是否为空,若为空则创建一个长度为1024的char型的缓冲区
    3. 将当前单个字符传入缓冲区中的第一位存储
    4. 调用write(writeBuffer, 0, 1)方法
        public void write(int c) throws IOException {
            synchronized (lock) {
                if (writeBuffer == null){
                    writeBuffer = new char[WRITE_BUFFER_SIZE];
                }
                writeBuffer[0] = (char) c;
                write(writeBuffer, 0, 1);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    写入字符串
    1. 锁住当前实例
    2. 创建一个cbuf字符数组
    3. 判断写入字符串要保存的长度是否小于等于1024,若大于则cbuf为创建出来的传入长度的字符数组(如:cbuf = new char[1200])
    4. 判断当前临时缓冲区是否为空,空则创建一个长度为1024的字符类的数组作为缓冲区
    5. cbuf字符数组被赋值为当前的缓冲区
    6. 调用传入字符串的getChars方法
    7. 调用write(cbuf, 0, len)方法
        public void write(String str, int off, int len) throws IOException {
            synchronized (lock) {
                char cbuf[];
                if (len <= WRITE_BUFFER_SIZE) {
                    if (writeBuffer == null) {
                        writeBuffer = new char[WRITE_BUFFER_SIZE];
                    }
                    cbuf = writeBuffer;
                } else {    // Don't permanently allocate very large buffers.
                    cbuf = new char[len];
                }
                str.getChars(off, (off + len), cbuf, 0);
                write(cbuf, 0, len);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    附加字符串:append方法

    从源码上看append方法还是去调用write方法进行写入

        public Writer append(CharSequence csq) throws IOException {
            write(String.valueOf(csq));
            return this;
        }
    
    • 1
    • 2
    • 3
    • 4

    刷新流

    public abstract void flush() throws IOException;
    
    • 1

    关闭流

    关闭流,先刷新。一旦流关闭,进一步的write()或flush()调用将引发IOException。

    public abstract void close() throws IOException;
    
    • 1

    返回一个丢弃所有字符的新Writer:nullWriter方法

    返回一个丢弃所有字符的新Writer。返回的流最初是打开的。通过调用close()方法关闭流。随后对close()的调用无效。 当流处于打开状态时,append(char)、append(CharSequence)、append(CharSequence,int,int)、flush()、write(int)、writ(char[])和write(char[[],int,int])方法什么都不做。流关闭后,这些方法都会抛出IOException

    public static Writer nullWriter() {
            return new Writer() {
                private volatile boolean closed;
    
                private void ensureOpen() throws IOException {
                    if (closed) {
                        throw new IOException("Stream closed");
                    }
                }
    
                @Override
                public Writer append(char c) throws IOException {
                    ensureOpen();
                    return this;
                }
    
                @Override
                public Writer append(CharSequence csq) throws IOException {
                    ensureOpen();
                    return this;
                }
    
                @Override
                public Writer append(CharSequence csq, int start, int end) throws IOException {
                    ensureOpen();
                    if (csq != null) {
                        Objects.checkFromToIndex(start, end, csq.length());
                    }
                    return this;
                }
    
                @Override
                public void write(int c) throws IOException {
                    ensureOpen();
                }
    
                @Override
                public void write(char[] cbuf, int off, int len) throws IOException {
                    Objects.checkFromIndexSize(off, len, cbuf.length);
                    ensureOpen();
                }
    
                @Override
                public void write(String str) throws IOException {
                    Objects.requireNonNull(str);
                    ensureOpen();
                }
    
                @Override
                public void write(String str, int off, int len) throws IOException {
                    Objects.checkFromIndexSize(off, len, str.length());
                    ensureOpen();
                }
    
                @Override
                public void flush() throws IOException {
                    ensureOpen();
                }
    
                @Override
                public void close() throws IOException {
                    closed = true;
                }
            };
        }
    
    • 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

    从源码中我们发现在nullWriter方法内new了Writer类,重写里面各个方法

    1. 增加closed属性标识流是否已关闭
    2. ensureClosed方法判断是否已关闭流,closed为true则已关闭抛出IO异常
    3. append方法直接返回当前实例
    4. write方法和flush方法都仅是调用ensureClosed方法
    5. close方法直接将closed属性设置为true

    StringWriter类

    一种字符流,将其输出收集在字符串缓冲区中,然后可用于构造字符串。 关闭StringWriter无效。该类中的方法可以在流关闭后调用,而不会生成IOException

    StringWriter继承Writer类

    字符串缓冲区

    字符串缓冲区指的是StringBuffer,我们可以将其理解为是一个容器用于存储String字符串,并且我们可以对其做各类操作

    private StringBuffer buf;
    
    • 1

    无参构造方法

    1. 无参构造会创建一个StringBuffer类型的buf缓冲区
    2. 锁获取当前buf缓冲区
    public StringWriter() {
            buf = new StringBuffer();
            lock = buf;
        }
    
    • 1
    • 2
    • 3
    • 4

    构造方法

    使用指定的初始字符串缓冲区大小创建新的字符串编写器。
    参数initialSize表示:在自动展开缓冲区之前,将放入该缓冲区的字符值的数量

        public StringWriter(int initialSize) {
            if (initialSize < 0) {
                throw new IllegalArgumentException("Negative buffer size");
            }
            buf = new StringBuffer(initialSize);
            lock = buf;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 若传入初始化缓冲区长度小于0,抛出非法参数异常
    2. buf的大小为传入缓冲区的长度
    3. 锁获取当前buf缓冲区

    方法

    写单个字符

    1. 我们注意到的是,写单个字符调用的是当前的buf的append方法,而buf是StringBuffer类型的,那么说明写单个字符StringWriter是调用的了StringBuffer的append方法
    2. StringBuffer中的append方法则继续调用父AbstractStringBuilder的append方法
    3. AbstractStringBuilder中首先会调用ensureCapacityInternal方法来对容器进行扩容
    4. 存储的容器value数组将字符存储起来(注意value数组是个byte[]
    5. 最终AbstractStringBuilder返回到StringBuffer再回到StringWriter
    public void write(int c) {
            buf.append((char) c);
        }
    ===========StringBuffer=================
    @Override
        @HotSpotIntrinsicCandidate
        public synchronized StringBuffer append(char c) {
            toStringCache = null;
            super.append(c);
            return this;
        }
    =============AbstractStringBuilder========
    @Override
        public AbstractStringBuilder append(char c) {
            ensureCapacityInternal(count + 1);
            if (isLatin1() && StringLatin1.canEncode(c)) {
                value[count++] = (byte)c;
            } else {
                if (isLatin1()) {
                    inflate();
                }
                StringUTF16.putCharSB(value, count++, c);
            }
            return this;
        }
    
    private void ensureCapacityInternal(int minimumCapacity) {
            // overflow-conscious code
            int oldCapacity = value.length >> coder;
            if (minimumCapacity - oldCapacity > 0) {
                value = Arrays.copyOf(value,
                        newCapacity(minimumCapacity) << coder);
            }
        }
    
    • 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

    由此我们得出若使用StringWriter存储单字符,其实是存入了AbstractStringBuilder的byte[] value

    写一个(部分)字符串

    1. 写一个字符串其实和单字符很像,毕竟String就是char[]
    2. 同样走到AbstractStringBuilder append方法,这里就不一样了,会调用putStringAt方法,然后调用String的getBytes方法通过System类将字符串复制到byte数组中
    public void write(String str) {
            buf.append(str);
        }
    ===================================
    public AbstractStringBuilder append(String str) {
            if (str == null) {
                return appendNull();
            }
            int len = str.length();
            ensureCapacityInternal(count + len);
            putStringAt(count, str);
            count += len;
            return this;
        }
        
        private final void putStringAt(int index, String str) {
            if (getCoder() != str.coder()) {
                inflate();
            }
            str.getBytes(value, index, coder);
        }
    ================String===============
    void getBytes(byte dst[], int dstBegin, byte coder) {
            if (coder() == coder) {
                System.arraycopy(value, 0, dst, dstBegin << coder, value.length);
            } else {    // this.coder == LATIN && coder == UTF16
                StringLatin1.inflate(value, 0, dst, dstBegin, value.length);
            }
        }
    
    • 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

    写入字符数组的一部分

    这里和写入单字符即更加像了,而最后的appendChars方法就是循环进行单字符的写入

    public void write(char cbuf[], int off, int len) {
            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
                ((off + len) > cbuf.length) || ((off + len) < 0)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return;
            }
            buf.append(cbuf, off, len);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    将字符串序列进行附加

    由于字符序列可以使用String.valueOf转为String型所有就是使用的写入字符串的方式进行附加

    public StringWriter append(CharSequence csq) {
            write(String.valueOf(csq));
            return this;
        }
    
    • 1
    • 2
    • 3
    • 4

    附加字符、附加字符序列的子序列

    附加字符就是再写入单字符
    附加子序列同样是写入字符串

    获取字符串缓冲区

    public StringBuffer getBuffer() {
            return buf;
        }
    
    • 1
    • 2
    • 3
  • 相关阅读:
    苹果cms大橙子vfed 5.0去授权完美破解主题模板
    [Python]`threading.local`创建线程本地数据
    测试之CSDN AI生成的GIT博文
    mysql数据库
    Neo4j:入门基础(二)~ 数据导入Neo4J
    exness:欧元区经济意外向好,欧元震荡蓄势等待突破
    物体颜色的来源
    SpringMvc第五战-【SpringMvcJSR303和拦截器】
    数据结构与算法-队列
    VMOS虚拟机开源,游戏安全面临新挑战
  • 原文地址:https://blog.csdn.net/qq_51553982/article/details/127605103