文档章节

BufferedReader剖析

此鱼不得水
 此鱼不得水
发布于 2015/07/05 00:56
字数 2876
阅读 29
收藏 0

以下内容转载自:http://www.cnblogs.com/skywang12345/p/io_23.html


BufferedReader 是缓冲字符输入流。它继承于Reader。
BufferedReader 的作用是为其他字符输入流添加一些缓冲功能。

BufferedReader 函数列表

复制代码

BufferedReader(Reader in)
BufferedReader(Reader in, int size)void     close()void     mark(int markLimit)boolean  markSupported()int      read()int      read(char[] buffer, int offset, int length)
String   readLine()boolean  ready()void     reset()long     skip(long charCount)

复制代码

 

BufferedReader 源码分析(基于jdk1.7.40)

View Code

说明
要 想读懂BufferReader的源码,就要先理解它的思想。BufferReader的作用是为其它Reader提供缓冲功能。创建 BufferReader时,我们会通过它的构造函数指定某个Reader为参数。BufferReader会将该Reader中的数据分批读取,每次读 取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从Reader中读取下一部分的数据。
为什么需要缓冲呢?原因很简单,效率问题!缓冲中的数据实际上是保存在内存中,而原始数据可能是保存在硬盘或NandFlash中;而我们知道,从内存中读取数据的速度比从硬盘读取数据的速度至少快10倍以上。
那干嘛不干脆一次性将全部数据都读取到缓冲中呢?第一,读取全部的数据所需要的时间可能会很长。第二,内存价格很贵,容量不想硬盘那么大。


下面,我就BufferReader中最重要的函数fill()进行说明。其它的函数很容易理解,我就不详细介绍了,大家可以参考源码中的注释进行理解。我们先看看fill()的源码:

复制代码

 1 private void fill() throws IOException { 2     int dst; 3     if (markedChar <= UNMARKED) { 4         /* No mark */ 5         dst = 0; 6     } else { 7         /* Marked */ 8         int delta = nextChar - markedChar; 9         if (delta >= readAheadLimit) {10             /* Gone past read-ahead limit: Invalidate mark */11             markedChar = INVALIDATED;12             readAheadLimit = 0;13             dst = 0;14         } else {15             if (readAheadLimit <= cb.length) {16                 /* Shuffle in the current buffer */17                 System.arraycopy(cb, markedChar, cb, 0, delta);18                 markedChar = 0;19                 dst = delta;20             } else {21                 /* Reallocate buffer to accommodate read-ahead limit */22                 char ncb[] = new char[readAheadLimit];23                 System.arraycopy(cb, markedChar, ncb, 0, delta);24                 cb = ncb;25                 markedChar = 0;26                 dst = delta;27             }28             nextChar = nChars = delta;29         }30     }31 32     int n;33     do {34         n = in.read(cb, dst, cb.length - dst);35     } while (n == 0);36     if (n > 0) {37         nChars = dst + n;38         nextChar = dst;39     }40 }

复制代码

根据fill()中的if...else...,我将fill()分为4种情况进行说明。

 

情况1:读取完缓冲区的数据,并且缓冲区没有被标记
执行流程如下,
(01) 其它函数调用 fill(),来更新缓冲区的数据
(02) fill() 执行代码 if (markedChar <= UNMARKED) { ... }
为了方便分析,我们将这种情况下fill()执行的操作等价于以下代码:

复制代码

 1 private void fill() throws IOException { 2     int dst; 3     if (markedChar <= UNMARKED) { 4         /* No mark */ 5         dst = 0; 6     } 
 7  8     int n; 9     do {10         n = in.read(cb, dst, cb.length - dst);11     } while (n == 0);12 13     if (n > 0) {14         nChars = dst + n;15         nextChar = dst;16     }17 }

复制代码

说明
这 种情况发生的情况是 — — Reader中有很长的数据,我们每次从中读取一部分数据到缓冲中进行操作。每次当我们读取完缓冲中的数据之后,并且此时BufferedReader没 有被标记;那么,就接着从Reader(BufferReader提供缓冲功能的Reader)中读取下一部分的数据到缓冲中。
其中,判断是否读完缓冲区中的数据,是通过“比较nextChar和nChars之间大小”来判断的。其中,nChars 是缓冲区中字符的总的个数,而 nextChar 是缓冲区中下一个要读取的字符的位置。
     判断BufferedReader有没有被标记,是通过“markedChar”来判断的。
理解这个思想之后,我们再对这种情况下的fill()的代码进行分析,就特别容易理解了。
(01) if (markedChar <= UNMARKED) 它的作用是判断“BufferedReader是否被标记”。若被标记,则dst=0。
(02) in.read(cb, dst, cb.length - dst) 等价于 in.read(cb, 0, cb.length),意思是从Reader对象in中读取cb.length个数据,并存储到缓冲区cb中,而且从缓冲区cb的位置0开始存储。该函数 返回值等于n,也就是n表示实际读取的字符个数。若n=0(即没有读取到数据),则继续读取,直到读到数据为止。
(03) nChars=dst+n 等价于 nChars=n;意味着,更新缓冲区数据cb之后,设置nChars(缓冲区的数据个数)为n。
(04) nextChar=dst 等价于 nextChar=0;意味着,更新缓冲区数据cb之后,设置nextChar(缓冲区中下一个会被读取的字符的索引值)为0。

 

情况2:读取完缓冲区的数据,缓冲区的标记位置>0,并且“当前标记的长度”超过“标记上限(readAheadLimit)”
执行流程如下,
(01) 其它函数调用 fill(),来更新缓冲区的数据
(02) fill() 执行代码 if (delta >= readAheadLimit) { ... }
为了方便分析,我们将这种情况下fill()执行的操作等价于以下代码:

复制代码

 1 private void fill() throws IOException { 2     int dst; 3     if (markedChar > UNMARKED) { 4         int delta = nextChar - markedChar; 5         if (delta >= readAheadLimit) { 6             markedChar = INVALIDATED; 7             readAheadLimit = 0; 8             dst = 0; 9         } 
10     }11 12     int n;13     do {14         n = in.read(cb, dst, cb.length - dst);15     } while (n == 0);16     if (n > 0) {17         nChars = dst + n;18         nextChar = dst;19     }20 }

复制代码

说明
这 种情况发生的情况是 — — BufferedReader中有很长的数据,我们每次从中读取一部分数据到缓冲区中进行操作。当我们读取完缓冲区中的数据之后,并且此 时,BufferedReader存在标记时,同时,“当前标记的长度”大于“标记上限”;那么,就发生情况2。此时,我们会丢弃“标记”并更新缓冲区。
(01) delta = nextChar - markedChar;其中,delta就是“当前标记的长度”,它是“下一个被读取字符的位置”减去“被标记的位置”的差值。
(02) if (delta >= readAheadLimit);其中,当delta >= readAheadLimit,就意味着,“当前标记的长度”>=“标记上限”。为什么要有标记上限,即readAheadLimit的值到底有何 意义呢?
    我们标记一个位置之后,更新缓冲区的时候,被标记的位置会被保存;当我们不停的更新缓冲区的时候,被标记的位置会被不停的放大。然后内存的容量是有效的,我们不可能不限制长度的存储标记。所以,需要readAheadLimit来限制标记长度!
(03) in.read(cb, dst, cb.length - dst) 等价于 in.read(cb, 0, cb.length),意思是从Reader对象in中读取cb.length个数据,并存储到缓冲区cb中,而且从缓冲区cb的位置0开始存储。该函数 返回值等于n,也就是n表示实际读取的字符个数。若n=0(即没有读取到数据),则继续读取,直到读到数据为止。
(04) nChars=dst+n 等价于 nChars=n;意味着,更新缓冲区数据cb之后,设置nChars(缓冲区的数据个数)为n。
(05) nextChar=dst 等价于 nextChar=0;意味着,更新缓冲区数据cb之后,设置nextChar(缓冲区中下一个会被读取的字符的索引值)为0。

 

情况3:读取完缓冲区的数据,缓冲区的标记位置>0,“当前标记的长度”没超过“标记上限(readAheadLimit)”,并且“标记上限(readAheadLimit)”小于/等于“缓冲的长度”;
执行流程如下,
(01) 其它函数调用 fill(),来更新缓冲区的数据
(02) fill() 执行代码 if (readAheadLimit <= cb.length) { ... }
为了方便分析,我们将这种情况下fill()执行的操作等价于以下代码:

复制代码

 1 private void fill() throws IOException { 2     int dst; 3     if (markedChar > UNMARKED) { 4         int delta = nextChar - markedChar; 5         if ((delta < readAheadLimit) &&  (readAheadLimit <= cb.length) ) { 6             System.arraycopy(cb, markedChar, cb, 0, delta); 7             markedChar = 0; 8             dst = delta; 9 10             nextChar = nChars = delta;11         }12     }13 14     int n;15     do {16         n = in.read(cb, dst, cb.length - dst);17     } while (n == 0);18     if (n > 0) {19         nChars = dst + n;20         nextChar = dst;21     }22 }

复制代码

说明
这 种情况发生的情况是 — — BufferedReader中有很长的数据,我们每次从中读取一部分数据到缓冲区中进行操作。当我们读取完缓冲区中的数据之后,并且此 时,BufferedReader存在标记时,同时,“当前标记的长度”小于“标记上限”,并且“标记上限”小于/等于“缓冲区长度”;那么,就发生情况 3。此时,我们保留“被标记的位置”(即,保留被标记位置开始的数据),并更新缓冲区(将新增的数据,追加到保留的数据之后)。

 

情况4:读取完缓冲区的数据,缓冲区的标记位置>0,“当前标记的长度”没超过“标记上限(readAheadLimit)”,并且“标记上限(readAheadLimit)”大于“缓冲的长度”;
执行流程如下,
(01) 其它函数调用 fill(),来更新缓冲区的数据
(02) fill() 执行代码 else { char ncb[] = new char[readAheadLimit]; ... }
为了方便分析,我们将这种情况下fill()执行的操作等价于以下代码:

复制代码

 1 private void fill() throws IOException { 2     int dst; 3     if (markedChar > UNMARKED) { 4         int delta = nextChar - markedChar; 5         if ((delta < readAheadLimit) &&  (readAheadLimit > cb.length) ) { 6             char ncb[] = new char[readAheadLimit]; 7             System.arraycopy(cb, markedChar, ncb, 0, delta); 8             cb = ncb; 9             markedChar = 0;10             dst = delta;11             12             nextChar = nChars = delta;13         }14     }15 16     int n;17     do {18         n = in.read(cb, dst, cb.length - dst);19     } while (n == 0);20     if (n > 0) {21         nChars = dst + n;22         nextChar = dst;23     }24 }

复制代码

说明
这 种情况发生的情况是 — — BufferedReader中有很长的数据,我们每次从中读取一部分数据到缓冲区中进行操作。当我们读取完缓冲区中的数据之后,并且此 时,BufferedReader存在标记时,同时,“当前标记的长度”小于“标记上限”,并且“标记上限”大于“缓冲区长度”;那么,就发生情况4。此 时,我们要先更新缓冲区的大小,然后再保留“被标记的位置”(即,保留被标记位置开始的数据),并更新缓冲区数据(将新增的数据,追加到保留的数据之 后)。

 

示例代码

关于BufferedReader中API的详细用法,参考示例代码(BufferedReaderTest.java): 

复制代码

 1 import java.io.BufferedReader; 2 import java.io.ByteArrayInputStream; 3 import java.io.File; 4 import java.io.InputStream; 5 import java.io.FileReader; 6 import java.io.IOException; 7 import java.io.FileNotFoundException; 8 import java.lang.SecurityException; 9 10 /**11  * BufferedReader 测试程序12  *13  * @author skywang14  */15 public class BufferedReaderTest {16 17     private static final int LEN = 5;18 19     public static void main(String[] args) {20         testBufferedReader() ;21     }22 23     /**24      * BufferedReader的API测试函数25      */26     private static void testBufferedReader() {27 28         // 创建BufferedReader字符流,内容是ArrayLetters数组29         try {30             File file = new File("bufferedreader.txt");31             BufferedReader in =32                   new BufferedReader(33                       new FileReader(file));34 35             // 从字符流中读取5个字符。“abcde”36             for (int i=0; i<LEN; i++) {37                 // 若能继续读取下一个字符,则读取下一个字符38                 if (in.ready()) {39                     // 读取“字符流的下一个字符”40                     int tmp = in.read();41                     System.out.printf("%d : %c\n", i, tmp);42                 }43             }44 45             // 若“该字符流”不支持标记功能,则直接退出46             if (!in.markSupported()) {47                 System.out.println("make not supported!");48                 return ;49             }50               51             // 标记“当前索引位置”,即标记第6个位置的元素--“f”52             // 1024对应marklimit53             in.mark(1024);54 55             // 跳过22个字符。56             in.skip(22);57 58             // 读取5个字符59             char[] buf = new char[LEN];60             in.read(buf, 0, LEN);61             System.out.printf("buf=%s\n", String.valueOf(buf));62             // 读取该行剩余的数据63             System.out.printf("readLine=%s\n", in.readLine());64 65             // 重置“输入流的索引”为mark()所标记的位置,即重置到“f”处。66             in.reset();67             // 从“重置后的字符流”中读取5个字符到buf中。即读取“fghij”68             in.read(buf, 0, LEN);69             System.out.printf("buf=%s\n", String.valueOf(buf));70 71             in.close();72        } catch (FileNotFoundException e) {73            e.printStackTrace();74        } catch (SecurityException e) {75            e.printStackTrace();76        } catch (IOException e) {77            e.printStackTrace();78        }79     }80 }

复制代码

程序中读取的bufferedreader.txt的内容如下:

abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ

运行结果
0 : a
1 : b
2 : c
3 : d
4 : e
buf=01234
readLine=56789
buf=fghij

 


本文转载自:http://www.cnblogs.com/skywang12345/p/io_23.html

共有 人打赏支持
此鱼不得水
粉丝 2
博文 41
码字总数 23991
作品 0
洛阳
私信 提问
JSON传送与接收(java)

在 JSON 中我们已经介绍过其基本格式,与XML相同,JSON只是一个文字格式,只要客户端与伺服端可以剖析它,就可以利用它作为传送数据的格式,但它是JavaScript的核心特性之一,所以在JavaScr...

giraffe_zj
2008/11/20
0
0
关于java在Linux下文件读取的问题

java语言,程序在Windows下编写,部署在RedHat下的Was上;我现在要在Linux下的一个目录内读取一个文件,试了十多种方法,结果都不行。。。但我在ubuntu上都是可行的。下面是简要的几种思路:...

沈珈右
2012/10/29
7.9K
7
JAVA读取外部资源的方法

在java代码中经常有读取外部资源的要求:如配置文件等等,通常会把配置文件放在classpath下或者在web项目中放在web-inf下. 1.从当前的工作目录中读取: try { BufferedReader in = new Buffere...

晨曦之光
2012/04/25
1K
1
JAVA怎么打开TXT逐行读取字符串

拿到文本的BufferedReader,然后使用BufferedReader的readLine()方法,就可以获得一行文本字符串,没有内容的时候会返回null,遍历执行就可以输出文本的所有内容。 1·拿到BufferedReader 2·...

周田
2016/07/05
18
0
BufferedReader中文乱码问题解决

BufferedReader读取输入流的时候, 如:BufferedReader buffer = new BufferedReader(in); 可能会出现中文乱码,这个时候,要加编码; BufferedReader buffer = new BufferedReader(in,"utf......

小狼狗007
2016/12/14
9
0

没有更多内容

加载失败,请刷新页面

加载更多

多命令链命令参数

Commands: --install <link> <name> <path> <priority> [--slave <link> <name> <path>] ... 在系统中加入一组替换项. --remove <name> <path> 从 <名......

Pulsar-V
32分钟前
0
0
【转】go get命令使用socket代理

由于某些不可描述的原因,国内使用go get命令安装某些包的时候会超时导致失败,比如net包、sys包、tools包等。第一种解决办法就是自己从git上下载后添加链接到GOPATH中,比如: 1234...

yiduwangkai
34分钟前
0
0
Windows同步对象Event和Linux的条件变量

最近在看一些同步对象模拟的东东,特别对在Windows下如何模拟条件变量折腾了很久。 1 Windows同步对象Event 微软有一个很有意思的同步对象,某种程度上和Linux的条件变量很相似。但秉承微软一...

shzwork
42分钟前
1
0
从上往下打印出二叉树的每个节点,同层节点从左至右打印。

//第一种做法 public class Solution { public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) { ArrayList <Integer> li=new ArrayList<Integer>(); ArrayList <TreeN......

南桥北木
51分钟前
1
0
linux 服务管理 Crontba、Ntpdate、Logrotate、Supervisor

crond linux 系统则是由 cron (crond) 这个系统服务来控制的。Linux 系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的。 另外, 由于使用者自己也可以设置计划任务,所以,...

狼王黄师傅
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部