文档章节

Java IO类库之BufferedWriter

 老韭菜
发布于 08/18 17:05
字数 1767
阅读 6
收藏 3

一、BufferedWriter介绍

    BufferedWriter继承自Writer类是字符缓冲输出流,它通过在内部创建一个字符缓冲区(char数组)为底层绑定的其他字符输出流Writer提供缓冲的功能,在不要求字符数据即时写入情况下可以实现单个字符、数组、字符串的高效写入。他提供字符、数组等高效写入的原理:若不在内部提供一个缓冲区,那么每次写入操作都要直接操作底层字符输出流Writer将字符转为字节数据然后向目的文件或者其他目标写入数据每写入一次都要先建立连接,访问磁盘,无疑效率是非常低下的,BufferedWriter通过在内部创建一个字符缓冲区,每次写入的时候先保存到内部缓冲区中,当到达一定数量时再将缓冲区中的字符数据一次性写入到绑定的底层字符输出流中,这样虽然不能实现数据实时写入到磁盘但提升了效率减少了磁盘访问次数。

二、BufferedWriter成员变量

public class BufferedWriter extends Writer {
    //底层绑定的字符输出流
    private Writer out;
    //内部字符数据缓冲区
    private char cb[];
    //缓冲区大小
    private int nChars;
    //缓冲区下一个字符写入位置
    int nextChar;
    //默认缓冲区大小
    private static int defaultCharBufferSize = 8192;

    //行分隔符
    private String lineSeparator;

}

三、BufferedWriter源码分析

1 - 构造函数

    /**
     * 构造函数,指定绑定的底层字符输出流,创建一个默认大小的字符输出缓冲区
     */
    public BufferedWriter(Writer out) {
        this(out, defaultCharBufferSize);
    }

    /**
     * 构造函数,指定绑定的底层字符输出流,创建指定大小的字符输出缓冲区
     */
    public BufferedWriter(Writer out, int sz) {
        super(out);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.out = out;
        cb = new char[sz];
        nChars = sz;
        nextChar = 0;

        lineSeparator = java.security.AccessController.doPrivileged(
            new sun.security.action.GetPropertyAction("line.separator"));
    }

    由于第一个构造方法内部调用的其实也是第二个构造方法,我们直接分析下第二个构造方法BufferedWriter(Writer out, int sz),分析源码该方法内部首先调用了父类构造方法super(out)该方法将BufferedWrite的锁对象引用lock指向方法指定的底层字符输出流对象out,接着对方法的缓冲区大小参数sz进行合法性校验,若小于0抛出异常,接下来就是绑定底层字符输出流out,创建指定长度sz缓冲区数组,初始化缓冲区下一个字符写入位置nextChar和缓冲区大小naChars。

2 -void write(int c)

    public void write(int c) throws IOException {
        synchronized (lock) {
            //检查流状态
            ensureOpen();
            //缓冲区是否写满,若满则刷新缓冲区
            if (nextChar >= nChars)
                flushBuffer();
            //在缓冲区数组写入字符
            cb[nextChar++] = (char) c;
        }
    }

    这里方法内部首先调用ensureOpen方法检测流是否已经关闭,若未关闭判断缓冲区是否写满,若已经写满则调用flushBuffer方法刷新缓冲区,我们进入该方法源码看下实现:

    void flushBuffer() throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (nextChar == 0)
                return;
            out.write(cb, 0, nextChar);
            nextChar = 0;
        }
    }

    flushBuffer方法判断了nextChar是否为0若为0则说明缓冲区还没有写入过或者之前已经刷新过流但还没写入数据,否则调用out.write方法将当前字符缓冲区数据写入到底层字符输出流out中重置缓冲区下一个字符写入位置nextChar为0,重复利用缓冲区,覆盖缓冲区原有的字符数据。

    分析完flushBuffer方法再回到调用方write方法继续往下分析,最后将指定字符插入缓冲区数组cb的当前写入位置。

    总结write(int c)方法的基础逻辑是首先判断BufferedWriter的流状态,若流已经关闭(即底层字符输出流为null)则抛出异常,否则判断缓冲区是否写满若写满则将缓冲区字符数据写入到底层字符输出流后重置缓冲区重新从字符缓冲区数组开头写入字符数据,最后将指定字符c写入缓冲区。

 

3 - 其他成员方法

    /** 检测BufferedWriter是否关闭 **/
    private void ensureOpen() throws IOException {
        if (out == null)
            throw new IOException("Stream closed");
    }

    /**
     * 刷新内部的字符输出缓冲区,将缓冲区字符数据写入底层输出流中,并重置缓冲区下一个写入字符位置nextChar
     */
    void flushBuffer() throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (nextChar == 0)
                return;
            out.write(cb, 0, nextChar);
            nextChar = 0;
        }
    }

    /**
     * 取最小值
     */
    private int min(int a, int b) {
        if (a < b) return a;
        return b;
    }

    /**
     * 写入字符数组的一部分,从下标off开始的len个字符
     */
    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;
            }
            //若字符数组写入的字符个数len大于缓冲区大小,刷新当前缓冲区将缓冲区字符数据写入到底层字符输出流,重置缓
            //冲区,之后将要写入的字符数组数据直接写入底层字符输出流
            if (len >= nChars) {
                flushBuffer();
                out.write(cbuf, off, len);
                return;
            }
            //若写入的字符个数小于缓冲区大小则将数据先写入缓冲区中,在缓冲区写满时将数据写入底层字节输出流,然后重置
            //缓冲区,继续写入
            int b = off, t = off + len;
            while (b < t) {
                int d = min(nChars - nextChar, t - b);
                System.arraycopy(cbuf, b, cb, nextChar, d);
                b += d;
                nextChar += d;
                if (nextChar >= nChars)
                    flushBuffer();
            }
        }
    }

    /**
     * 写入一个字符串的一部分,从下标off开始的len个字符写入到内部字符缓冲区cb中
     */
    public void write(String s, int off, int len) throws IOException {
        synchronized (lock) {
            ensureOpen();

            int b = off, t = off + len;
            while (b < t) {
                //获取缓冲区剩余写入空间和写入字符个数len的较小值
                int d = min(nChars - nextChar, t - b);
                //将字符串方法参数指定部分写入到缓冲区
                s.getChars(b, b + d, cb, nextChar);
                b += d;
                nextChar += d;
                //判断缓冲区是否写满,若写满刷新缓冲区字符数据到底层字符输出流,重置缓冲区后继续写入直到写完
                if (nextChar >= nChars)
                    flushBuffer();
            }
        }
    }

    /**
     * 写入一个行分隔符
     */
    public void newLine() throws IOException {
        write(lineSeparator);
    }

    /**
     * 刷新流,刷新缓冲区数据将其写入底层字符输出流中
     */
    public void flush() throws IOException {
        synchronized (lock) {
            flushBuffer();
            out.flush();
        }
    }

    /**
     * 关闭流,主要是将刷新缓冲区将未写入底层字符输出流的数据写入底层字符输出流,然后将底层字符输出流和缓冲区设置为
     * null释放对象资源
     */
    public void close() throws IOException {
        synchronized (lock) {
            if (out == null) {
                return;
            }
            try (Writer w = out) {
                flushBuffer();
            } finally {
                out = null;
                cb = null;
            }
        }
    }

 

© 著作权归作者所有

共有 人打赏支持
粉丝 12
博文 64
码字总数 107681
作品 0
杭州
后端工程师
私信 提问
java.io几种读写文件的方式

一、Java把这些不同来源和目标的数据都统一抽象为数据流。   Java语言的输入输出功能是十分强大而灵活的。   在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输入输...

知止内明
08/06
0
0
哪里出错了?帮忙看下 谢谢

import java.io.*; import java.util.*; public class TextTa2 { public static void main(String[] args) { we(); rd(); } private static void rd( ) { //定义一个读取字符的类rd FileRead......

林俊仁
2012/05/29
129
7
黑马程序员----输入输出流IO(一)

-------------------------android培训、java培训、期待与您交流! ------------------------- IO是输入input和输出output的简写 流按照操作数据分为两种:字节流与字符流 流按流向分为:输入...

长平狐
2013/07/01
145
0
《Kotin 极简教程》第15章 Kotlin 文件IO操作、正则表达式与多线程

第15章 Kotlin 文件IO操作与多线程 《Kotlin极简教程》正式上架: 点击这里 > 去京东商城购买阅读 点击这里 > 去天猫商城购买阅读 非常感谢您亲爱的读者,大家请多支持!!!有任何问题,欢迎...

程序员诗人
2017/07/24
0
0
学Android开发,入门语言java知识点

Android是一种以Linux为基础的开源码操作系统,主要使用于便携设备,而linux是用c语言和少量汇编语言写成的,如果你想研究Android,就去学java语言吧。 Android开发入门教程 -Java语言,最差...

抉择很难
2015/12/11
185
0

没有更多内容

加载失败,请刷新页面

加载更多

崛起于Springboot2.X之通讯WebSocket(40)

技术简介:Springboot2.0.3+freemaker+websocket 1、添加pom依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-bo......

木九天
18分钟前
1
0
Java常用四大线程池用法以及ThreadPoolExecutor详解

为什么用线程池? 1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处-理效率 2.线程并发数量过多,抢占系统资源从而导致阻塞 3.对线程进行一些简单的管理 在Java中...

孟飞阳
20分钟前
1
0
Netty+Websocket 实现一个简易聊天室

后台代码 /** * 服务端 */public class ChatServer {public static void main(String[] args) throws Exception {int port=8080; //服务端默认端口new ChatServer().bind...

这很耳东先生
22分钟前
2
0
一个本科学生对Linux的认知

我是一名大三的普通一本大学的软件工程的一名学生,学校开设了一些关于系统开发的课程,纸上得来终觉浅,学校的课程课时较短,想要在56个课时之内学会一些公司需要的技能,无疑是纸上谈兵,一...

linuxprobe16
23分钟前
1
0
如何选择开源许可证?

如何为代码选择开源许可证,这是一个问题。 世界上的开源许可证,大概有上百种。很少有人搞得清楚它们的区别。即使在最流行的六种----GPL、BSD、MIT、Mozilla、Apache和LGPL----之中做选择,...

吴伟祥
25分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部