`
wdhdmx
  • 浏览: 304460 次
  • 性别: Icon_minigender_1
  • 来自: 山西
博客专栏
D4bbb5f7-9aa4-3e66-8194-f61b3f0241c2
天天编程
浏览量:21974
社区版块
存档分类
最新评论

字符流(二)BufferedReader和BufferedWriter源码理解

阅读更多

1.BufferedReader

1.1 继承关系

public class BufferedReader extends Reader {
//这个又是装饰模式
private Reader in;
}

1.2 构造方法

public BufferedReader(Reader in) {
	this(in, defaultCharBufferSize);
}
//默认缓存数组的大小
private static int defaultCharBufferSize = 8192;
//构造方法
public BufferedReader(Reader in, int sz) {
        //这个方法可参考前面的Writer源码,只要是将锁赋值
	super(in);
	if (sz <= 0)
	    throw new IllegalArgumentException("Buffer size <= 0");
	//装饰模式。。
	this.in = in;
	cb = new char[sz];
	nextChar = nChars = 0;
}
//两个上面用到的参数,用于缓存数据,是字符(char)数组,不是字节(byte)数组。
private char cb[];
private int nChars, nextChar;

1.3 标记有关

在看read方法之前先看一眼 标记mark有关的方法有点帮助。为看懂read做铺垫

//标记流中的当前位置,带入的参数表示标记所占的空间
public void mark(int readAheadLimit) throws IOException {
	if (readAheadLimit < 0) {
	    throw new IllegalArgumentException("Read-ahead limit < 0");
	}
	synchronized (lock) {
	    ensureOpen();
	    this.readAheadLimit = readAheadLimit;
	    markedChar = nextChar;
	    markedSkipLF = skipLF;
	}
}
//回到标记位置
public void reset() throws IOException {
	synchronized (lock) {
	    ensureOpen();
	    if (markedChar < 0)
		throw new IOException((markedChar == INVALIDATED)
				      ? "Mark invalid"
				      : "Stream not marked");
            //下面两个参数在读方法中会有详细解释
	    nextChar = markedChar;
	    skipLF = markedSkipLF;
	}
}

 

1.4 read

这个方法中fill()是重点,有点绕,但看懂后就觉得很清晰,能完全理解bufferedReader的原理。

看完这个方法再回去看3.3的标记部分,就很容易看懂。

public int read() throws IOException {
        //锁,看来读得时候也只能一个方法读。
	synchronized (lock) {
	    //确保输入流不是空。
	    ensureOpen();
	    //这个循环和while一样。
	    for (;;) {
                //下面的判断为是否下一个读取的字符超出了缓存数组中实际包含数据的大小。
		if (nextChar >= nChars) {
                    //下一个字符超出或者等于缓存数组的大小
                    //这个是核心的方法,里面有标记的内容,详细的看下面内容。
		    fill();
                    //如果还是超出,则表示输入流读完了。
		    if (nextChar >= nChars)
			return -1;
		}
                //如果下一个字符是换行符.这个变量只有在readLine里面才变为true。和\n\r有关,可忽略。针对不同的平台的
		if (skipLF) {
		    skipLF = false;
		    if (cb[nextChar] == '\n') {
			nextChar++;
			continue;
		    }
		}
                //返回当前读的字符,并将要读字符+1
		return cb[nextChar++];
	    }
	}
}

//下面的变量是用于fill方法里的
//下面两个变量是标记的状态, -1为未启动标记,-2为标记失效。
private static final int INVALIDATED = -2;
private static final int UNMARKED = -1;
//标记的位置
private int markedChar = UNMARKED;
//nChars表示现在缓存数组中已经存在多少个字符。
//nextChar表示下一个读取的位置,从0开始,这个只是缓存数组中的位置,并不是读取流的位置。
private int nChars, nextChar;
//标记分配的空间大小。超出后,如果缓存数组重新处置,则标记失效。
private int readAheadLimit = 0;

//将字符数组读满,然后直接返回数组中的某个值。里面主要考虑的是标记的问题。
//这个和BufferedInputStream差不多,一个是byte[],这个是char[]
private void fill() throws IOException {
        //计算这次缓存数据的起始位置,起始位置之前保存的是标记的内容。
	int dst;
	if (markedChar <= UNMARKED) {
	    //这里表示没有使用标记,或者标记失效。
	    dst = 0;
	} else {
	    //表示使用标记
	    //这个变量表示标记之后实际使用了多少空间
	    int delta = nextChar - markedChar;
	    if (delta >= readAheadLimit) {
	        //如果超过了标记初始的空间。
                //标记失效
                markedChar = INVALIDATED;
                //标记空间赋0
		readAheadLimit = 0;
		//缓存数据起点0
		dst = 0;
	    } else {
	        //如果未超过标记初始的空间。
	        if (readAheadLimit <= cb.length) {
                    //分配的标记空间小于缓存数组的长度
		    //将标记后实际使用长度复制到数组的开始。
		    System.arraycopy(cb, markedChar, cb, 0, delta);
		    //将标记的位置赋0,标记所占空间仍然是原来的空间,不会缩小。
		    markedChar = 0;
		    //数据缓存的起点
		    dst = delta;
		} else {
		    //长度不够,新建一个。
		    char ncb[] = new char[readAheadLimit];
		    //和上面一样,复制标记到最前面
		    System.arraycopy(cb, markedChar, ncb, 0, delta);
		    //将引用更新
		    cb = ncb;
		    markedChar = 0;
		    dst = delta;
		}
                nextChar = nChars = delta;
	    }
	}
	//下面是读数据,读出一定长度,默认cb的长度8192,cb在BufferedReader中是唯一的缓存数组。
	int n;
	//这个地方读的方法中不可能返回0.所以只会执行一次
	do {
	    //从标签之后读,读满cb字符数组,注意,这里是调用in的读方法。
	    n = in.read(cb, dst, cb.length - dst);
	} while (n == 0);
        //读到数据的情况,没有读到的话就不做任何操作。
	if (n > 0) {
            //现在缓存空间中已有的真实缓存数量
	    nChars = dst + n;
            //下一个读取的位置。
	    nextChar = dst;
	}
}

其它的read方法和这个类似。

一次读出很多字符的时候,处理的策略是:

a.缓存数组不够,就用in直接读,不经过缓存.

b.缓存数组够,就将缓存中读出。

c.缓存数组够,但读完后还没读满,则继续从in中接着读,不够的部分不过缓存数组。

 

1.5 readLine

这个是用的比较多的方法,所以列出来。这个方法在有上面的基础上,还是很好懂的。

String readLine(boolean ignoreLF) throws IOException {
        //传入的布尔值默认为false
	StringBuffer s = null;
	int startChar;

        synchronized (lock) {
            ensureOpen();
	    boolean omitLF = ignoreLF || skipLF;
        //这个是什么?goto?
	bufferLoop:
            //while
	    for (;;) {
                //下一个字符超出缓存数组大小,这里nextChar是从0开始的,所以相等的时候就代表已经超出了缓存数组范围。
		if (nextChar >= nChars)
		    fill();
		//下面的if是判断流的末尾,读完了就返回null,或者将之前读的内容返回
		if (nextChar >= nChars) {
		    if (s != null && s.length() > 0)
			return s.toString();
		    else
			return null;
		}
		//表示没有到末尾.
		boolean eol = false;
		char c = 0;
		int i;
                //这个是处理\r\n的情况,不进行两次判断,忽略
		if (omitLF && (cb[nextChar] == '\n')) 
                    nextChar++;
		skipLF = false;
		omitLF = false;

	    charLoop:
		//遍历缓存数组,直到\n或者\r
		for (i = nextChar; i < nChars; i++) {
		    c = cb[i];
		    if ((c == '\n') || (c == '\r')) {
			//表示读取到了换行
			eol = true;
			break charLoop;
		    }
		}
                //记录读取开始的地方,
		startChar = nextChar;
		//要读的下一个字符。
		nextChar = i;
		//读取到换行,而不是读完缓存。
		if (eol) {
		    String str;
		    if (s == null) {
			str = new String(cb, startChar, i - startChar);
		    } else {
			s.append(cb, startChar, i - startChar);
			str = s.toString();
		    }
		    nextChar++;
		    if (c == '\r') {
			skipLF = true;
		    }
		    return str;
		}
		//表示读完了缓存数组,还需要继续读。
		if (s == null) 
		    s = new StringBuffer(defaultExpectedLineLength);
		s.append(cb, startChar, i - startChar);
	    }
        }
    }

1.6 其它方法

skip 就是先把缓存数组中跳过去,如果缓存数组不够,就再将数据读入缓存数组,再跳,一直循环。

ready 表示缓存是否读完了,没什么用处。

markSupported 是否支持标记

close 流等需要关闭的东西都关闭。

 

2.BufferedWriter

2.1 继承关系

public class BufferedWriter extends Writer {
    //装饰模式
    private Writer out;
}

2.2 构造函数

将缓存数组初始化,并且根据平台初始化换行符号。

    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;
        //获取换行的符号\n \r,和方法System.getProperty("line.separator")一样
	lineSeparator =	(String) java.security.AccessController.doPrivileged(
               new sun.security.action.GetPropertyAction("line.separator"));
    }

2.3 write有关

public void write(int c) throws IOException {
	synchronized (lock) {
            //判断是否有输出流
	    ensureOpen();
            //如果缓存数组写满了,就flush数组。
	    if (nextChar >= nChars)
		flushBuffer();
            //将内容写入缓存数组中
	    cb[nextChar++] = (char) c;
	}
}
//flush,这个方法就知道flush的重要性,
void flushBuffer() throws IOException {
	synchronized (lock) {
	    ensureOpen();
	    if (nextChar == 0)
		return;
            //将数据写入流
	    out.write(cb, 0, nextChar);
	    nextChar = 0;
	}
}

用的比较多的写字符串。就是将字符串转变成字符数组。

public void write(String s, int off, int len) throws IOException {
	synchronized (lock) {
	    ensureOpen();
            
	    int b = off, t = off + len;
	    while (b < t) {
		int d = min(nChars - nextChar, t - b);
                //将字符串转为字符数组
		s.getChars(b, b + d, cb, nextChar);
                //写入缓存数组
		b += d;
		nextChar += d;
		if (nextChar >= nChars)
                    //如果写满了,就写入流中。
		    flushBuffer();
	    }
	}
}

2.4 其它

a.writeLine 写一个换行

public void newLine() throws IOException {
        //同样写到缓存数组里
	write(lineSeparator);
}

b.flush,这个也不多说了。

public void flush() throws IOException {
	synchronized (lock) {
	    flushBuffer();
	    out.flush();
	}
}

c.close 关闭所有该关闭的.

public void close() throws IOException {
	synchronized (lock) {
	    if (out == null) {
		return;
	    }
	    try {
                //最后还释放了一次。不过没有执行flush方法,所以在close前还是要执行一次flush!
	        flushBuffer();
	    } finally {
	        out.close();
	        out = null;
	        cb = null;
	    }
	}
}

3.结束

看完这个字符流,清晰了好多,开始看的比较慢,但是后来越来越快,水到渠成。

 

 

 

 

 

 

1
0
分享到:
评论

相关推荐

    BufferedReader 和BufferedWriter 实例

    与`BufferedReader`类似,我们通常会先创建一个`FileOutputStream`,然后通过`OutputStreamWriter`将其转换为字符流,最后将`OutputStreamWriter`传递给`BufferedWriter`。 ```java FileOutputStream fos = new ...

    字节流字符流的使用方法源码+文档

    6. **文档**:提供的Word文档很可能是对源码的详细解释,包括步骤、注意事项和可能遇到的问题,这有助于理解和学习字节流和字符流的使用。 总的来说,这个压缩包提供了一个很好的学习机会,你可以通过实际运行代码...

    Java IO 字节流 字符流

    总的来说,理解Java的IO系统,特别是字节流和字符流的概念,对于开发高效、可靠的程序至关重要。通过选择合适的流类型、使用缓冲流和桥接流,以及利用工具类,开发者可以更好地处理各种数据输入输出需求。在实际项目...

    Java 输入输出流 源码

    3. **字符流**:Reader和Writer是所有字符流的基类,例如 FileReader 和 FileWriter。字符流内部使用了编码解码机制,适合处理文本数据。BufferedReader和BufferedWriter用于提高读写效率,通过缓冲区批量处理数据。...

    Java BufferedWriter BufferedReader 源码分析

    Java的BufferedWriter和BufferedReader是Java IO流中用于提高读写效率的两个关键类。它们都通过内部缓冲区来批量处理数据,从而减少对底层流的直接操作次数,提高性能。 **BufferedWriter** BufferedWriter是一个...

    Java io输入输出流及字符集

    过滤字符流,如InputStreamReader和OutputStreamWriter,用于在字节流和字符流之间转换。 二、字符集 字符集是表示字符的规则集合,Java使用Unicode字符集。常见的字符集有ASCII、GBK、UTF-8等。在Java中,字符流...

    JAVA范例 七)输入/输出流---字符流

    标签中的"源码"可能指的是查看和理解这些流类的内部实现,这对于深入学习和调试问题非常有帮助。而"工具"可能指的是利用这些流类来构建实用的I/O工具,比如文件复制、格式转换等。 在处理运行时需要加的包,确保在...

    Android应用源码之IOStreamSample.zip

    在Android系统中,IO流遵循Java的标准IO模型,分为字节流和字符流两大类。字节流处理的是8位的字节数据,包括InputStream和OutputStream两个抽象基类,分别用于输入和输出。字符流则处理16位的Unicode字符,对应的...

    Java-se-io学习 Java学习资料源码

    Reader和Writer接口是字符流的父接口,具体实现如BufferedReader和BufferedWriter,它们提供了缓冲功能,提高了读写效率。 3. 转换流:InputStreamReader和OutputStreamWriter是字节流到字符流的桥梁,可以指定字符...

    字节流,字符流,对象流,序列化,持久化

    在Java编程语言中,字节流、字符流、对象流和序列化是处理数据传输和存储的核心概念。这些概念在程序设计中起着至关重要的作用,尤其是在进行输入输出操作时。下面将对这些主题进行详细解释。 1. 字节流(Byte ...

    JavaSE的项目源码、主要是文件的IO操作! Java学习资料

    对于字符流,BufferedReader和BufferedWriter提供了类似的功能。 文件复制是一个常见的I/O操作,可以使用FileInputStream和FileOutputStream结合DataInputStream和DataOutputStream实现,或者使用NIO(New IO)框架...

    java 写的字符编码转换工具(附带源码)

    7. **源码分析**:提供的源码可以作为学习和理解字符编码转换机制的一个实例。通过阅读源码,我们可以深入理解Java如何处理编码转换,以及如何设计和实现这样一个工具。 这个工具可能包含了一个命令行界面或者图形...

    IO流

    - InputStreamReader和OutputStreamWriter:作为字节流和字符流之间的桥梁,它们分别将字节流转换为字符流和反之。 3. 滤流器流(Filter Stream): - FilterInputStream和FilterOutputStream:提供了对字节流...

    Java中的那些“流”之JAVA基础知识之我的理解8

    例如,通过网络接收数据时,可能需要先使用Socket建立连接,然后通过InputStream读取字节,再通过InputStreamReader和BufferedReader转化为字符流进行处理。 在标签中提到的“源码”部分,理解流的工作原理有时需要...

    io流详解代码

    - 字符流:Reader和Writer是所有字符输入流和输出流的基类,FileReader和FileWriter用于读写文本文件。 - 缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter提供缓冲功能,提高...

    Java-IO流高级-例题 & 例题源码 & PPT教学文档(黑马程序员详细版).rar

    转换流允许在字节流和字符流之间转换,如InputStreamReader/OutputStreamWriter,它们在字节流与字符流之间建立桥梁,使得我们可以使用特定的字符集进行编码和解码。 5. **对象流(Object Stream)** 对象流如...

    21.【缓冲流、转换流、序列化流、打印流】(1)_缓冲流_源码

    Java中的转换流(`InputStreamReader`和`OutputStreamWriter`)用于在字节流和字符流之间进行转换。它们是连接字节流和字符流的桥梁,因为Java的I/O系统是基于字节的,但字符集如UTF-8、GBK等是以字符为单位的。...

    零基础学习JAVA源码

    通过以上内容的学习,你将能够熟练掌握Java语言的基础,并具备阅读和理解Java源码的能力。同时,随着实践经验的积累,你将逐渐深入到更高级的主题,如设计模式、框架应用、数据库连接等,成为一名合格的Java开发者。

Global site tag (gtag.js) - Google Analytics