最近在做一个文件检索功能, 发现BufferReader的效率挺高的,但是也存在一些小问题, 所以现在把自己对BufferReader的一些东西做一下学习,
这样就可以自己进行BufferReader的改造以满足业务
BufferReader的主体的功能有三个:
1.readLine: 就是先读取一段char[] 数组到内存, 然后检索内存中的 \n 或者 \r\n 如果找到了就把这一行返回
2.read: 把他缓存的char[]中的一个char返回
3. public int read(char[] buffer, int offset, int length) 从缓存的char[]中读取一段char []数组
如果buffer不足, 会通过fillbuf()函数从硬盘中获取buf,然后再进行这个操作.
通过使用内存换取io , 提高了读写效率.
BufferReader主要使用了三个标志来进行缓存读取的判断
* { X X X X X X X X X X X X - - }
* ^ ^ ^
* | | |
* mark pos end</pre>
*
* pos 当前 游标所在的位置 每当一次read 就会pos 前进一段
* end 是char[] 数组的尾巴,到了这个就要进行fillbuf了
* mark 配合reset使用, 把pos回滚到mark的位置
那么一readLine为例详细分析一下,其大体顺序如下:
readline的功能主要是 以 /r/n 或者 /n 为结束标志来读取文本中每一行
1.其他地方调用readline
2.使用checkNotClosed检查是否这个BufferReader是否已经关闭
3.开始进行一次fillBuf() 如果 pos ==end 或者 fillbuf()返回的是-1 那么就进行直接返回null
4.如果填充的buf不是空, 那么就开始遍历buf 知道遇到 /n 记录这个位置就可以向string中添加char并返回了.
具体注释代码如下;
/**
* Returns the next line of text available from this reader. A line is
* represented by zero or more characters followed by {@code '\n'},
* {@code '\r'}, {@code "\r\n"} or the end of the reader. The string does
* not include the newline sequence.
* 以 /r/n 或者 /n 为结束标志来读取文本中每一行
* @return the contents of the line or {@code null} if no characters were
* read before the end of the reader has been reached.
* @throws IOException
* if this reader is closed or some other I/O error occurs.
*/
public String readLine() throws IOException {
synchronized (lock) {
checkNotClosed();//检查是否reader已经关闭
/* has the underlying stream been exhausted? 还有buf可以读吗? */
if (pos == end && fillBuf() == -1) {
return null;
}
int count = 0;
for (int charPos = pos; charPos < end; charPos++) {
//charPos 是 指向了 char[] 这个buf的哪个位置
count ++;
if (count > 1024 * 9) {//如果超出了 大约 3k汉字都没有回车,就终止
Log.e("TAG", "OOps, the line is too large!" + this.getClass().getName());
return null;
}
char ch = buf[charPos];
if (ch > '\r') {//一直没有遇到 \n\r
continue;
}
if (ch == '\n') { // \n的情况
String res = new String(buf, pos, charPos - pos);
pos = charPos + 1;
return res;
} else if (ch == '\r') { // \r\n的情况
String res = new String(buf, pos, charPos - pos);
pos = charPos + 1;
if (((pos < end) || (fillBuf() != -1))
&& (buf[pos] == '\n')) {
pos++;
}
return res;
}
}
char eol = '\0';
StringBuilder result = new StringBuilder(80);
/* Typical Line Length */
result.append(buf, pos, end - pos);
while (true) {//如果找不到 \n 就会不断的fillbuf 不断的添加到string中直到 文件末尾
count ++;
if (count > 1024 * 9) {
Log.e("TAG", "OOps, the line is too large!" + this.getClass().getName());
return null;
}
pos = end;
/* Are there buffered characters available? */
if (eol == '\n') {
return result.toString();
}
// attempt to fill buffer
if (fillBuf() == -1) {//没有找到 /n 只能继续找了, 如没有了就返回
// characters or null.
return result.length() > 0 || eol != '\0'
? result.toString()
: null;
}
for (int charPos = pos; charPos < end; charPos++) {//这时候pos和end都变了, 继续填充找 \n
char c = buf[charPos];
if (eol == '\0') {
if ((c == '\n' || c == '\r')) {
eol = c;
}
} else if (eol == '\r' && c == '\n') {
if (charPos > pos) {
result.append(buf, pos, charPos - pos - 1);
}
pos = charPos + 1;
return result.toString();
} else {
if (charPos > pos) {
result.append(buf, pos, charPos - pos - 1);
}
pos = charPos;
return result.toString();
}
}
if (eol == '\0') { //应该是文件结束就返回 拼接的字符串
result.append(buf, pos, end - pos);
} else {
result.append(buf, pos, end - pos - 1);
}
}
}
}
/**
* Populates the buffer with data. It is an error to call this method when
* the buffer still contains data; ie. if {@code pos < end}.
* 把文本文件添加到char[] 数组中
* @return the number of bytes read into the buffer, or -1 if the end of the
* source stream has been reached.
*/
private int fillBuf() throws IOException {
// assert(pos == end);
//buf是在构造函数中初始化的, 默认是 8k
if (mark == -1 || (pos - mark >= markLimit)) {
/* mark isn't set or has exceeded its limit. use the whole buffer mark 没有设置 或者已经超过了 那个limit, 直接读取buf的长度*/
int result = in.read(buf, 0, buf.length);
if (result > 0) {
mark = -1;
pos = 0;
end = result; //把end 设置为读取到的 buf的长度
}
return result;
}
//
if (mark == 0 && markLimit > buf.length) {
/* the only way to make room when mark=0 is by growing the buffer 如果 markLimit很大 超过 当前的buf.length, 就需要 扩充 buf了 */
int newLength = buf.length * 2;
if (newLength > markLimit) {
newLength = markLimit;
}
char[] newbuf = new char[newLength];
System.arraycopy(buf, 0, newbuf, 0, buf.length);
buf = newbuf;
} else if (mark > 0) {
//mark > 0 就 截断buf 获取 mark 后面的 buf 从mark以后开始计算buf
/* make room by shifting the buffered data to left mark positions */
System.arraycopy(buf, mark, buf, 0, buf.length - mark);
pos -= mark;//回到mark的位置
end -= mark;
mark = 0;
}
/* Set the new position and mark position */
int count = in.read(buf, pos, buf.length - pos);
if (count != -1) {
end += count;
}
return count;
}
JAVA中mark()和reset()用法的通俗理解
mark就像书签一样,在这个BufferedReader对应的buffer里作个标记,以后再调用reset时就可以再回到这个mark过的地方。mark方法有个参数,通过这个整型参数,你告诉系统,希望在读出这么多个字符之前,这个mark保持有效。读过这么多字符之后,系统可以使mark不再有效,而你不能觉得奇怪或怪罪它。这跟buffer有关,如果你需要很长的距离,那么系统就必须分配很大的buffer来保持你的mark。
//eg.
//reader is a BufferedReader
reader.mark(50);//要求在50个字符之内,这个mark应该保持有效,系统会保证buffer至少可以存储50个字符
int a = reader.read();//读了一个字符
int b = reader.read();//又读了一个字符
//做了某些处理,发现需要再读一次
reader.reset();
reader.read();//读到的字符和a相同
reader.read();//读到的字符和b相同