在我们学IO字符流时,讲到了字符流的缓冲区,它能使读取和写入的速度加快(因为内置缓冲区,不需要来回从内存和目的去取元素,类似于中转站),并且提供了一些符合字符特点且便捷的方法,如newLine和readLine方法,都是原来字符流没有的。现在,便模拟读取流的缓冲区,进行自定义的读和读行方法。
我们知道BufferedReader不是FileReader下的子类对象。这里用到了装饰设计模式。如果因为只是多增加一个缓冲功能便对FileReader进行继承,继承体系会越来越臃肿,试想,如果只为增加一个功能,便对整个继续体系进行继续继承,那有点太耗资源了。所以,继承也有不灵活的地方。因此,我们只对缓冲功能进行单独的封装,谁要这个功能,便和谁关联,这样不是更灵活么。这便是装饰设计模式的一种例子。
接下来我们分析下缓冲区的原理。BufferedReader对象在读取数据时是这样的:
BufferedReader br = new BufferedReader(new FileReader(file));
int ch = 0;
while ((ch = br.read()) != -1) {
System.out.println((char)ch);
}
FileReader比它读取时多了一个对数组的操作。其实,缓冲区就是定义了一个数组,并对外提供了更多的方法对数组进行访问。而这些方法最终操作的都是数组的角标。即缓冲区每次从源中读取一批数据进数组,然后提供外部对元素的操作动作(都是基于角标的)。如果这批数据被外部对象取完之后,缓冲区便会再次从源中读取一批数据。当源中无数据可取时,便返回-1作为结束标记。
如图,当每读完一个数据,则角标就移至下一位置。但是,每次取都不一定是满值,也就是缓冲区中的数组未必都能转满,因此,我们需要再定义一个计数器,以统计每次读取的个数,根据计数器来判断是否取完源中的数据。
public class MyBufferedReader {
FileReader fr;
//缓冲数组的角标
private int index;
//读取到的字符数
private int count = 0;
//缓冲数组
char[] chs = new char[1024];
public MyBufferedReader(FileReader fr) {
this.fr = fr;
}
public int myRead() throws IOException{
//count为0,说明还未开始读或者已经读完一次缓冲区,需要将数组角标重置为0
if (count == 0) {
//抓取一堆数据进缓冲区
count = fr.read(chs);
index = 0;
}
//如果没读到数据,说明源中已无数据可取,返回-1作为结束标记
if (count < 0) {
return -1;
}
//从缓冲区中读出数据
char ch = chs[index];
//每读取一个,角标就 +1,移至下一元素,而元素个数则 -1
index ++;
count --;
return ch;
}
}
这样,便完成了read的模拟程序。接下来对readLine方法进行编写。
有了read的方法之后,readLine的模拟不会很难。引用read方法就有了缓冲区,接下来只要有个容易来装这些读取的数据,到换行标记时,再输出即可。
于是我们想到用StringBuild进行对元素的暂时存储,当读至换行标记,再进行输出。换行标记是“\r”和“\n”,读到这两个字符我们都不存,读到“\n”时就进行输出。
public String myReadLine() throws IOException{
//存储元素的“容器”
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = myRead()) != -1) {
//当读取'\r'时,循环进入下一次迭代
if (ch == '\r')
continue;
//换行标记读完,则把读到的行内容返回
if (ch == '\n') {
return sb.toString();
}
//存储元素
sb.append((char)ch);
}
if (sb.length() != 0) {//①
return sb.toString();
}
return null;
}
当读至①时,也就是最后一行,如果没有换行标记,也就没有'\r'和'\n',这样便读不到if (ch == '\n')后面的输出语句,那最后一句便不能输出,因此,增加此判断,当最后“容器”还有内容时,就将内容输出。
最后是关闭流,其底层关闭的就是FileReader,即
public void myClose() throws IOException {
fr.close();
}
以上,便完成了对字符流缓冲区的分析和模拟。