java io进修(二十二) BufferedReader(字符缓冲输入流)
当前位置:以往代写 > JAVA 教程 >java io进修(二十二) BufferedReader(字符缓冲输入流)
2019-06-14

java io进修(二十二) BufferedReader(字符缓冲输入流)

java io进修(二十二) BufferedReader(字符缓冲输入流)

副标题#e#

BufferedReader 先容

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)

package java.io;
     
public class BufferedReader extends Reader {
     
    private Reader in;
     
    // 字符缓冲区
    private char cb[];
    // nChars 是cb缓冲区中字符的总的个数
    // nextChar 是下一个要读取的字符在cb缓冲区中的位置
    private int nChars, nextChar;
     
    // 暗示“标志无效”。它与UNMARKED的区别是:
    // (01) UNMARKED 是压根就没有配置过标志。
    // (02) 而INVALIDATED是配置了标志,可是被标志位置太长,导致标志无效!
    private static final int INVALIDATED = -2;
    // 暗示没有配置“标志”
    private static final int UNMARKED = -1;
    // “标志”
    private int markedChar = UNMARKED;
    // “标志”能标志位置的最大长度
    private int readAheadLimit = 0; /* Valid only when markedChar > 0 */
     
    // skipLF(即skip Line Feed)是“是否忽略换行符”标志
    private boolean skipLF = false;
     
    // 配置“标志”时,生存的skipLF的值
    private boolean markedSkipLF = false;
     
    // 默认字符缓冲区巨细
    private static int defaultCharBufferSize = 8192;
    // 默认每一行的字符个数
    private static int defaultExpectedLineLength = 80;
     
    // 建设“Reader”对应的BufferedReader工具,sz是BufferedReader的缓冲区巨细
    public BufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;
        cb = new char[sz];
        nextChar = nChars = 0;
    }
     
    // 建设“Reader”对应的BufferedReader工具,默认的BufferedReader缓冲区巨细是8k
    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);
    }
     
    // 确保“BufferedReader”是打开状态
    private void ensureOpen() throws IOException {
        if (in == null)
            throw new IOException("Stream closed");
    }
     
    // 填充缓冲区函数。有以下两种环境被挪用:
    // (01) 缓冲区没有数据时,通过fill()可以向缓冲区填凑数据。
    // (02) 缓冲区数据被读完,需更新时,通过fill()可以更新缓冲区的数据。
    private void fill() throws IOException {
        // dst暗示“cb中填凑数据的起始位置”。
        int dst;
        if (markedChar <= UNMARKED) {
            // 没有标志的环境,则设dst=0。
            dst = 0;
        } else {
            // delta暗示“当前标志的长度”,它便是“下一个被读取字符的位置”减去“标志的位置”的差值;
            int delta = nextChar - markedChar;
            if (delta >= readAheadLimit) {
                // 若“当前标志的长度”高出了“标志上限(readAheadLimit)”,
                // 则扬弃标志!
                markedChar = INVALIDATED;
                readAheadLimit = 0;
                dst = 0;
            } else {
                if (readAheadLimit <= cb.length) {
                    // 若“当前标志的长度”没有高出了“标志上限(readAheadLimit)”,
                    // 而且“标志上限(readAheadLimit)”小于/便是“缓冲的长度”;
                    // 则先将“下一个要被读取的位置,间隔我们标志的置符的间隔”间的字符生存到cb中。
                    System.arraycopy(cb, markedChar, cb, 0, delta);
                    markedChar = 0;
                    dst = delta;
                } else {
                    // 若“当前标志的长度”没有高出了“标志上限(readAheadLimit)”,
                    // 而且“标志上限(readAheadLimit)”大于“缓冲的长度”;
					// 查察本栏目

说明:

要想读懂BufferReader的源码,就要先领略它的思想。BufferReader的浸染是为其它Reader提供缓冲成果。建设BufferReader时,我们会通过它的结构函数指定某个Reader为参数。BufferReader会将该Reader中的数据分批读取,每次读取一部门到缓冲中;操纵完缓冲中的这部门数据之后,再从Reader中读取下一部门的数据。

为什么需要缓冲呢?原因很简朴,效率问题!缓冲中的数据实际上是生存在内存中,而原始数据大概是生存在硬盘或NandFlash中;而我们知道,从内存中读取数据的速度比从硬盘读取数据的速度至少快10倍以上。

#p#分页标题#e#

那干嘛不爽性一次性将全部数据都读取到缓冲中呢?第一,读取全部的数据所需要的时间大概会很长。第二,内存价值很贵,容量不想硬盘那么大。

下面,我就BufferReader中最重要的函数fill()举办说明。其它的函数很容易领略,我就不具体先容了,各人可以参考源码中的注释举办领略。我们先看看fill()的源码:

private void fill() throws IOException {
    int dst;
    if (markedChar <= UNMARKED) {
        /* No mark */
        dst = 0;
    } else {
        /* Marked */
        int delta = nextChar - markedChar;
        if (delta >= readAheadLimit) {
            /* Gone past read-ahead limit: Invalidate mark */
            markedChar = INVALIDATED;
            readAheadLimit = 0;
            dst = 0;
        } else {
            if (readAheadLimit <= cb.length) {
                /* Shuffle in the current buffer */
                System.arraycopy(cb, markedChar, cb, 0, delta);
                markedChar = 0;
                dst = delta;
            } else {
                /* Reallocate buffer to accommodate read-ahead limit */
                char ncb[] = new char[readAheadLimit];
                System.arraycopy(cb, markedChar, ncb, 0, delta);
                cb = ncb;
                markedChar = 0;
                dst = delta;
            }
            nextChar = nChars = delta;
        }
    }
     
    int n;
    do {
        n = in.read(cb, dst, cb.length - dst);
    } while (n == 0);
    if (n > 0) {
        nChars = dst + n;
        nextChar = dst;
    }
}

按照fill()中的if...else...,我将fill()分为4种环境举办说明。

环境1:读取完缓冲区的数据,而且缓冲区没有被标志

执行流程如下,

(01) 其它函数挪用 fill(),来更新缓冲区的数据

(02) fill() 执行代码 if (markedChar <= UNMARKED) { ... }

为了利便阐明,我们将这种环境下fill()执行的操纵等价于以下代码:

private void fill() throws IOException {
    int dst;
    if (markedChar <= UNMARKED) {
        /* No mark */
        dst = 0;
    } 
     
    int n;
    do {
        n = in.read(cb, dst, cb.length - dst);
    } while (n == 0);
     
    if (n > 0) {
        nChars = dst + n;
        nextChar = dst;
    }
}

说明:

这种环境产生的环境是 — — 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)”

#p#副标题#e#

执行流程如下,

(01) 其它函数挪用 fill(),来更新缓冲区的数据

(02) fill() 执行代码 if (delta >= readAheadLimit) { ... }

为了利便阐明,我们将这种环境下fill()执行的操纵等价于以下代码:

private void fill() throws IOException {
    int dst;
    if (markedChar > UNMARKED) {
        int delta = nextChar - markedChar;
        if (delta >= readAheadLimit) {
            markedChar = INVALIDATED;
            readAheadLimit = 0;
            dst = 0;
        } 
    }
     
    int n;
    do {
        n = in.read(cb, dst, cb.length - dst);
    } while (n == 0);
    if (n > 0) {
        nChars = dst + n;
        nextChar = dst;
    }
}

说明:

#p#分页标题#e#

这种环境产生的环境是 — — 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()执行的操纵等价于以下代码:

private void fill() throws IOException {
    int dst;
    if (markedChar > UNMARKED) {
        int delta = nextChar - markedChar;
        if ((delta < readAheadLimit) &&  (readAheadLimit <= cb.length) ) {
            System.arraycopy(cb, markedChar, cb, 0, delta);
            markedChar = 0;
            dst = delta;
     
            nextChar = nChars = delta;
        }
    }
     
    int n;
    do {
        n = in.read(cb, dst, cb.length - dst);
    } while (n == 0);
    if (n > 0) {
        nChars = dst + n;
        nextChar = dst;
    }
}

说明:

这种环境产生的环境是 — — BufferedReader中有很长的数据,我们每次从中读取一部门数据到缓冲区中举办操纵。当我们读取完缓冲区中的数据之后,而且此时,BufferedReader存在标志时,同时,“当前标志的长度”小于“标志上限”,而且“标志上限”小于/便是“缓冲区长度”;那么,就产生环境3。此时,我们保存“被标志的位置”(即,保存被标志位置开始的数据),并更新缓冲区(将新增的数据,追加到保存的数据之后)。

环境4:读取完缓冲区的数据,缓冲区的标志位置>0,“当前标志的长度”没高出“标志上限(readAheadLimit)”,而且“标志上限(readAheadLimit)”大于“缓冲的长度”;

执行流程如下,

(01) 其它函数挪用 fill(),来更新缓冲区的数据

(02) fill() 执行代码 else { char ncb[] = new char[readAheadLimit]; ... }

为了利便阐明,我们将这种环境下fill()执行的操纵等价于以下代码:

private void fill() throws IOException {
    int dst;
    if (markedChar > UNMARKED) {
        int delta = nextChar - markedChar;
        if ((delta < readAheadLimit) &&  (readAheadLimit > cb.length) ) {
            char ncb[] = new char[readAheadLimit];
            System.arraycopy(cb, markedChar, ncb, 0, delta);
            cb = ncb;
            markedChar = 0;
            dst = delta;
                 
            nextChar = nChars = delta;
        }
    }
     
    int n;
    do {
        n = in.read(cb, dst, cb.length - dst);
    } while (n == 0);
    if (n > 0) {
        nChars = dst + n;
        nextChar = dst;
    }
}

#p#副标题#e#

说明:

这种环境产生的环境是 — — BufferedReader中有很长的数据,我们每次从中读取一部门数据到缓冲区中举办操纵。当我们读取完缓冲区中的数据之后,而且此时,BufferedReader存在标志时,同时,“当前标志的长度”小于“标志上限”,而且“标志上限”大于“缓冲区长度”;那么,就产生环境4。此时,我们要先更新缓冲区的巨细,然后再保存“被标志的位置”(即,保存被标志位置开始的数据),并更新缓冲区数据(将新增的数据,追加到保存的数据之后)。

示例代码

#p#分页标题#e#

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

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.lang.SecurityException;
     
/**
 * BufferedReader 测试措施
 *
 * @author skywang
 */
public class BufferedReaderTest {
     
    private static final int LEN = 5;
     
    public static void main(String[] args) {
        testBufferedReader() ;
    }
     
    /**
     * BufferedReader的API测试函数
     */
    private static void testBufferedReader() {
     
        // 建设BufferedReader字符流,内容是ArrayLetters数组
        try {
            File file = new File("bufferedreader.txt");
            BufferedReader in =
                  new BufferedReader(
                      new FileReader(file));
     
            // 从字符流中读取5个字符。“abcde”
            for (int i=0; i<LEN; i++) {
                // 若能继承读取下一个字符,则读取下一个字符
                if (in.ready()) {
                    // 读取“字符流的下一个字符”
                    int tmp = in.read();
                    System.out.printf("%d : %c\n", i, tmp);
                }
            }
     
            // 若“该字符流”不支持标志成果,则直接退出
            if (!in.markSupported()) {
                System.out.println("make not supported!");
                return ;
            }
                   
            // 标志“当前索引位置”,即标志第6个位置的元素--“f”
            // 1024对应marklimit
            in.mark(1024);
     
            // 跳过22个字符。
            in.skip(22);
     
            // 读取5个字符
            char[] buf = new char[LEN];
            in.read(buf, 0, LEN);
            System.out.printf("buf=%s\n", String.valueOf(buf));
            // 读取该行剩余的数据
            System.out.printf("readLine=%s\n", in.readLine());
     
            // 重置“输入流的索引”为mark()所标志的位置,即重置到“f”处。
            in.reset();
            // 从“重置后的字符流”中读取5个字符到buf中。即读取“fghij”
            in.read(buf, 0, LEN);
            System.out.printf("buf=%s\n", String.valueOf(buf));
     
            in.close();
       } catch (FileNotFoundException e) {
           e.printStackTrace();
       } catch (SecurityException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       }
    }
}

措施中读取的bufferedreader.txt的内容如下:

abcdefghijklmnopqrstuvwxyz

0123456789

ABCDEFGHIJKLMNOPQRSTUVWXYZ

运行功效:

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

    关键字:

在线提交作业