Java IO源碼分析 - Reader,Writer系列(二)

說明

整個(gè)系列的文章全部參考或直接照搬下面兩位作者的文章,這里只是根據(jù)自己需要對原作者的文章梳理的總結(jié),僅給自己日后復(fù)習(xí)時(shí)提供思路,如有讀者看到學(xué)習(xí)時(shí)建議移步原作。再次重申并非我所寫

另兩篇本人總結(jié)的IO系列

HikariCP:Java I/O源碼分析 - InputStream,OutputStream系列
HikariCP:Java IO源碼分析 - Reader,Writer系列(一)

PipedReader,PipedWriter

PipedReader與PipedInputStream極其相似,PipedWriter與PipedOutputStream也極其相似。

PipedReader與PipedWriter分別為字符管道輸入流和字符管道輸出流。管道輸入流通過連接到管道輸出流實(shí)現(xiàn)了類似管道的功能,用于線程之間的通信。

通常,由某個(gè)線程向管道輸出流中寫入數(shù)據(jù)。根據(jù)管道的特性,這些數(shù)據(jù)會自動發(fā)送到與管道輸出流對應(yīng)的管道輸入流中。這時(shí)其他線程就可以從管道輸入流中讀取數(shù)據(jù),這樣就實(shí)現(xiàn)了線程之間的通信。

PipedReader為字符管道輸入流,用于讀取對應(yīng)的字符管道輸出流寫入其內(nèi)置字符緩存數(shù)組buffer中的字符、借此來實(shí)現(xiàn)線程之間的通信。

PipedWriter為字符管道輸出流、用于將當(dāng)前線程的指定字符寫入到與此線程對應(yīng)的管道字符輸入流中。

PipedReader

public class PipedReader extends Reader {
    //管道輸出流是否關(guān)閉
    boolean closedByWriter = false;
    //管道輸入流是否關(guān)閉
    boolean closedByReader = false;
    //管道輸入流是否被連接
    boolean connected = false;

    //從管道中讀取數(shù)據(jù)的線程
    Thread readSide;
    //向管道中寫入數(shù)據(jù)的線程
    Thread writeSide;

   /**
    * 管道循環(huán)輸入緩沖區(qū)的默認(rèn)大小。
    */
    private static final int DEFAULT_PIPE_SIZE = 1024;

    /**
     * 放置數(shù)據(jù)的循環(huán)緩沖區(qū)。
     */
    char buffer[];

    /**
     * 緩沖區(qū)的位置,當(dāng)從連接的管道輸出流中接收到下一個(gè)數(shù)據(jù)字符時(shí),會將其存儲到該位置。
     */
    int in = -1;

    /**
     * 緩沖區(qū)的位置,此管道輸入流將從該位置讀取下一個(gè)數(shù)據(jù)字節(jié)。
     */
    int out = 0;
    
    /**
     * 創(chuàng)建PipedReader,并指定其對應(yīng)的PipedWriter。
     */
    public PipedReader(PipedWriter src) throws IOException {
        this(src, DEFAULT_PIPE_SIZE);
    }

    /**
     * 創(chuàng)建一個(gè)PipedReader,使其連接到管道輸出流src,并指定管道大小為pipeSize。
     * @since      1.6
     */
    public PipedReader(PipedWriter src, int pipeSize) throws IOException {
        initPipe(pipeSize);
        connect(src);
    }


    /**
     * 創(chuàng)建尚未連接的PipedReader。
     */
    public PipedReader() {
        initPipe(DEFAULT_PIPE_SIZE);
    }

    /**
     * 創(chuàng)建一個(gè)尚未連接的PipedReader,并指定管道大小為pipeSize。
     * @since      1.6
     */
    public PipedReader(int pipeSize) {
        initPipe(pipeSize);
    }
}

對于Piped系列的字符字節(jié)輸入輸出流,剛開始對其中的in和out兩個(gè)變量還不太明白。第二遍刷類似的內(nèi)容,果然一目了然

receive,

/**
 * 接收一個(gè)字符,將其插入到緩沖區(qū)。如果沒有可用的輸入,方法會阻塞。
 */
synchronized void receive(int c) throws IOException {
    //檢查PipedReader的狀態(tài)是否正常。
    if (!connected) {
        throw new IOException("Pipe not connected");
    } else if (closedByWriter || closedByReader) {
        throw new IOException("Pipe closed");
    } else if (readSide != null && !readSide.isAlive()) {
        throw new IOException("Read end dead");
    }

    ///獲取將數(shù)據(jù)寫入管道的線程,狀態(tài)設(shè)置
    writeSide = Thread.currentThread();
    // 如果被寫入管道的數(shù)據(jù)剛好被讀完 或者
    // 管道已經(jīng)被塞滿 兩種情況
    while (in == out) {
        if ((readSide != null) && !readSide.isAlive()) {
            throw new IOException("Pipe broken");
        }
        /* full: kick any waiting readers */
        // 不分作用線程的情況直接喚醒一個(gè)跑,能跑通的跑就行
        notifyAll();
        try {
            wait(1000);
        } catch (InterruptedException ex) {
            throw new java.io.InterruptedIOException();
        }
    }
    //???
    if (in < 0) {
        in = 0;
        out = 0;
    }
    //將數(shù)據(jù)字節(jié)寫入到緩沖區(qū)中
    buffer[in++] = (char) c;
    //如果in已經(jīng)超出了緩沖區(qū)的范圍,將in置為0,從頭開始寫
    if (in >= buffer.length) {
        in = 0;
    }
}

對于函數(shù)中notifyAll()函數(shù)調(diào)用的解釋:

因?yàn)樵撦斎胼敵隽骶褪枪┒嗑€程數(shù)據(jù)交換使用,可能此時(shí)鎖外掛著一堆讀寫線程。全部喚醒,因?yàn)闊o論這兩種情況的哪一種不分線程是誰哪個(gè)能跑下去讓它跑就行,跑不了的全部wait 1秒。

疑問

對于這種處理方式的==疑問==:

雖然程序肯定會自己調(diào)通,但是不懂的是應(yīng)該這樣效率很差,為什么不用ReentrantLock解決。分成兩種鎖控制,根據(jù)不同的條件來控制作用線程的執(zhí)行?難道是因?yàn)闊o法判斷此時(shí)緩沖池是滿還是空?(自己想了想好像是無法判斷) 那新問題就是為什么不新加一個(gè)volatile變量控制當(dāng)前線程的前一次作用緩沖池的線程是writeSide還是readSide?

receivedLast,read,close


/**
 * 管道輸出流關(guān)閉時(shí)(PipedWriter.close()中會調(diào)用此方法),通知其已經(jīng)關(guān)閉。
 */
synchronized void receivedLast() {
    closedByWriter = true;
    notifyAll();
}

/**
 * 將最多l(xiāng)en個(gè)數(shù)據(jù)字節(jié)從此管道輸入流讀入char數(shù)組。
 * 
 * 如果已到達(dá)數(shù)據(jù)流的末尾,或者len超出管道緩沖區(qū)大小,則讀取的字符數(shù)將少于len。
 * 
 * 如果len為0,則不讀取任何字節(jié)并返回0;
 * 否則,在至少1個(gè)輸入字符可用、檢測到流末尾、拋出異常前,該方法將一直阻塞。
 */
public synchronized int read(char cbuf[], int off, int len)  throws IOException {
    // 狀態(tài)判斷
    if (!connected) {
        throw new IOException("Pipe not connected");
    } else if (closedByReader) {
        throw new IOException("Pipe closed");
    } else if (writeSide != null && !writeSide.isAlive()
               && !closedByWriter && (in < 0)) {
        throw new IOException("Write end dead");
    }

    // 參數(shù)判斷
    if ((off < 0) || (off > cbuf.length) || (len < 0) ||
        ((off + len) > cbuf.length) || ((off + len) < 0)) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return 0;
    }

    // 特殊情況判斷
    /* possibly wait on the first character */
    int c = read();
    if (c < 0) {
        return -1;
    }
    cbuf[off] =  (char)c;
    int rlen = 1;
    while ((in >= 0) && (--len > 0)) {
        cbuf[off + rlen] = buffer[out++];
        rlen++;
        if (out >= buffer.length) {
            out = 0;
        }
        if (in == out) {
            /* now empty */
            in = -1;
        }
    }
    return rlen;
}

/**
 * 關(guān)閉此傳送流并釋放與該流相關(guān)的所有系統(tǒng)資源。
 */
public void close()  throws IOException {
    in = -1;
    closedByReader = true;
}

從close的設(shè)計(jì)可以看出,由于早期設(shè)計(jì)架構(gòu)的思考,該類的開發(fā)人員明顯的清楚,該類對于輸入流操作的函數(shù)進(jìn)入與非執(zhí)行結(jié)束退出的關(guān)鍵判斷條件是in和closedByReader變量。所以這里只要對其狀態(tài)進(jìn)行設(shè)置即可。

感悟

寫代碼一定要像這些大師一樣,特別有條理,1、先對該P(yáng)ipedReader類進(jìn)行狀態(tài)的判斷,2、然后進(jìn)行輸入?yún)?shù)的判斷,3、然后進(jìn)行特殊情況的判斷處理,4、進(jìn)行程序優(yōu)化。可見目前自己青銅

總結(jié)

  • PipedReader和PipedInputStream的幾乎一模一樣。區(qū)別在于PipedReader操作的是字符,PipedInputStream操作的是字節(jié);
  • PipedReader有ready方法來判斷是否可以從PipedReader中讀數(shù)據(jù),而PipedInputStream則根據(jù)available()方法進(jìn)行判斷。

PipedWriter

flush

/**
 * 刷新此輸出流并強(qiáng)制寫出所有緩沖的輸出字節(jié)。
 * 這將通知所有讀取數(shù)據(jù)的線程,告知它們管道中的字符處于等待中。
 */
public synchronized void flush() throws IOException {
    if (sink != null) {
        if (sink.closedByReader || closed) {
            throw new IOException("Pipe closed");
        }
        synchronized (sink) {
            sink.notifyAll();
        }
    }
}

我們知道sink在PipedWriter類中指的是和其連接的PipedReader對象,也即兩者操作的是同一個(gè)對象。所以如果PipedReader類的當(dāng)前對象在進(jìn)行receive函數(shù)或者read函數(shù)被wait的時(shí)候會掛到該對象的線程等待集合中。此時(shí)緩沖池裝填可能是滿了,也可能空了。

所以當(dāng)flush被調(diào)用時(shí),明確的是該緩沖池中的數(shù)據(jù)要被讀走了。然后喚醒該對象等待隊(duì)列上的線程即可,無所謂喚醒的是讀線程還是寫線程,讀線程一定可以正常運(yùn)行下去,因?yàn)槠溥M(jìn)入wait情況的先決條件是in<0很明顯這時(shí)不可能,相反寫線程肯定會繼續(xù)卡住,因?yàn)槠溥M(jìn)入wait條件的先決條件是in==out很明顯這很有可能。

總結(jié)

  • PipedWriter和PipedOutputStream的幾乎一模一樣。區(qū)別在于PipedReader操作的是字符,PipedInputStream操作的是字節(jié)。

InputStreamReader,OutputStreamWriter

InputStreamReader和OutputStreamWriter 是字節(jié)流通向字符流的橋梁:它使用指定的 charset 讀寫字節(jié)并將其解碼為字符。

  • InputStreamReader 的作用是將“字節(jié)輸入流”轉(zhuǎn)換成“字符輸入流”。它繼承于Reader。
  • OutputStreamWriter 的作用是將“字節(jié)輸出流”轉(zhuǎn)換成“字符輸出流”。它繼承于Writer。

InputStreamReader

由于InputStreamReader類的函數(shù)全是依賴其內(nèi)部聲明的StreamDecoder對象來作用的。所以這里我們大概了解一下該類的各個(gè)函數(shù)的返回結(jié)果即可。

// 將“字節(jié)輸入流”轉(zhuǎn)換成“字符輸入流”
public class InputStreamReader extends Reader {
    // InputStreamReader的功能是依賴StreamDecoder完成的
    private final StreamDecoder sd;

    // 根據(jù)in創(chuàng)建InputStreamReader,使用默認(rèn)的編碼
    public InputStreamReader(InputStream in) {
        super(in);
        try {
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }
    
    // 獲取解碼器
    public String getEncoding() {
        return sd.getEncoding();
    }

    // 讀取并返回一個(gè)字符
    public int read() throws IOException {
        return sd.read();
    }

    // 將InputStreamReader中的數(shù)據(jù)寫入cbuf中,從cbuf的offset位置開始寫入,寫入長度是length
    public int read(char cbuf[], int offset, int length) throws IOException {
        return sd.read(cbuf, offset, length);
    }

    // 能否從InputStreamReader中讀取數(shù)據(jù)
    public boolean ready() throws IOException {
        return sd.ready();
    }

    // 關(guān)閉InputStreamReader
    public void close() throws IOException {
        sd.close();
    }
}

感覺像是給InutStream穿了個(gè)外衣。將字節(jié)流通過流解碼器StreamDecoder,解碼成了字符流。

OutputStreamWriter

OutputStreamWriter 作用和原理都與InputStreamReader一模一樣。
作用就是將“字節(jié)輸出流”轉(zhuǎn)換成“字符輸出流”。它的原理是,我們創(chuàng)建“字符輸出流”對象時(shí),會指定“字節(jié)輸出流”以及“字符編碼”。

演示程序

/**
 * InputStreamReader 和 OutputStreamWriter 測試程序
 *
 * @author skywang
 */
public class StreamConverter {

    private static final String FileName = "file.txt";
    private static final String CharsetName = "utf-8";
    //private static final String CharsetName = "gb2312";

    public static void main(String[] args) {
        testWrite();
        testRead();
    }

    /**
     * OutputStreamWriter 演示函數(shù)
     *
     */
    private static void testWrite() {
        try {
            // 創(chuàng)建文件“file.txt”對應(yīng)File對象
            File file = new File(FileName);
            // 創(chuàng)建FileOutputStream對應(yīng)OutputStreamWriter:將字節(jié)流轉(zhuǎn)換為字符流,即寫入out1的數(shù)據(jù)會自動由字節(jié)轉(zhuǎn)換為字符。
            OutputStreamWriter out1 = new OutputStreamWriter(new FileOutputStream(file), CharsetName);
            // 寫入10個(gè)漢字
            out1.write("字節(jié)流轉(zhuǎn)為字符流示例");
            // 向“文件中”寫入"0123456789"+換行符
            out1.write("0123456789\n");

            out1.close();

        } catch(IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * InputStreamReader 演示程序
     */
    private static void testRead() {
        try {
            // 方法1:新建FileInputStream對象
            // 新建文件“file.txt”對應(yīng)File對象
            File file = new File(FileName);
            InputStreamReader in1 = new InputStreamReader(new FileInputStream(file), CharsetName);

            // 測試read(),從中讀取一個(gè)字符
            char c1 = (char)in1.read();
            System.out.println("c1="+c1);

            // 測試skip(long byteCount),跳過4個(gè)字符
            in1.skip(6);

            // 測試read(char[] cbuf, int off, int len)
            char[] buf = new char[10];
            in1.read(buf, 0, buf.length);
            System.out.println("buf="+(new String(buf)));

            in1.close();
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
}

運(yùn)行結(jié)果:

c1=字
buf=流示例0123456

問題1,char為什么能表示中文呢

看了下面這篇文章很好理解這篇博文

答:因?yàn)閖ava是用unicode字符編碼表來對應(yīng)字符的,"字"這個(gè)中文字符的unicode就是2個(gè)字節(jié)(且所有收錄的中文都是2個(gè)字節(jié)的,本人粗略算了一下2萬多個(gè)收錄了)。且如果其給定的int值范圍超過0-65535的范圍,如果強(qiáng)制轉(zhuǎn)換(char c = (char)i;)其會自動mod 65536(此處只針對大于)。java會根據(jù)該規(guī)則自動轉(zhuǎn)換到0-65535區(qū)間中的一個(gè)。而“字”這個(gè)字的unicode編碼其實(shí)是23383。

問題2,UTF-8方式存儲的數(shù)據(jù),StreamDecoder類怎么精準(zhǔn)算出原數(shù)據(jù)占幾個(gè)字節(jié),并還原原始數(shù)據(jù)

看了下面這篇阮一峰老師的文章很好理解字符編碼筆記:ASCII,Unicode 和 UTF-8

答:也就是因?yàn)閁TF-8方式讀取字符的時(shí)候可以精準(zhǔn)的找到整個(gè)字符所占的所有字節(jié)呀!這也就是為什么它的中文占3甚至4個(gè)字節(jié)的原因,因?yàn)閁TF-8編碼方式的每個(gè)字節(jié)都有標(biāo)識位,需要耗費(fèi)一些空間。還是挺好理解的

重點(diǎn)3

一定要搞懂一件事:

Unicode 是「字符集」
UTF-8 是「編碼規(guī)則」

其中:

  • 字符集:為每一個(gè)「字符」分配一個(gè)唯一的 ID(學(xué)名為碼位 / 碼點(diǎn) / Code Point)
  • 編碼規(guī)則:將「碼位」轉(zhuǎn)換為字節(jié)序列的規(guī)則(編碼/解碼 可以理解為 加密/解密 的過程)

參考:
邱昊宇的回答

總結(jié)

  • InputStreamReader,字節(jié)流通向字符流的橋梁:它使用指定的charset讀取字節(jié)并將其解碼為字符。
  • 每次調(diào)用InputStreamReader中的read方法都會導(dǎo)致從底層輸入流讀取一個(gè)或多個(gè)字節(jié),然后調(diào)用編碼轉(zhuǎn)換器將字節(jié)轉(zhuǎn)化為字符。為避免頻繁調(diào)用轉(zhuǎn)換器(StreamDecoder類內(nèi)部底層調(diào)用),實(shí)現(xiàn)從字節(jié)到字符的高效轉(zhuǎn)換,可以提前從底層流讀取更多的字節(jié)。為了達(dá)到最高效率,可要考慮在BufferedReader內(nèi)包裝InputStreamReader。
  • InputStreamReader的功能是依賴于StreamDecoder完成的。
  • OutputStreamWriter,字符流通向字節(jié)流的橋梁:它使用指定的charset將要寫入流中的字符編碼成字節(jié)。
  • 每次調(diào)用write()方法都會導(dǎo)致在給定字符(或字符集)上調(diào)用編碼轉(zhuǎn)換器(StreamEncoder類內(nèi)部底層調(diào)用)。為避免頻繁調(diào)用轉(zhuǎn)換器,在寫入底層輸出流之前,可以將得到的這些字節(jié)積累在緩沖區(qū)。例如,可考慮將OutputStreamWriter包裝到BufferedWriter中。
  • OutputStreamWriter的功能是依賴于StreamEncoder完成的。

FileReader,F(xiàn)ileWriter

  • FileReader 是用于讀取字符流的類,它繼承于InputStreamReader。要讀取原始字節(jié)流,請考慮使用 FileInputStream。
  • FileWriter 是用于寫入字符流的類,它繼承于OutputStreamWriter。要寫入原始字節(jié)流,請考慮使用 FileOutputStream。

需要注意的是該類并非直接繼承自Reader,Writer類,而是繼承其子類并在其子類基礎(chǔ)上進(jìn)行擴(kuò)展。

大致看了一下源碼,我們可以看出FileReader是基于InputStreamReader實(shí)現(xiàn)的。相對的FileWriter是基于OutputStreamWriter實(shí)現(xiàn)的。

public class FileWriter extends OutputStreamWriter {

    public FileWriter(String fileName) throws IOException {
        super(new FileOutputStream(fileName));
    }
}

public class FileReader extends InputStreamReader {

    public FileReader(String fileName) throws FileNotFoundException {
        super(new FileInputStream(fileName));
    }
}

都是如此。只是幾個(gè)構(gòu)造函數(shù)參數(shù)不同

演示程序

FileWriter

// 創(chuàng)建文件“file.txt”對應(yīng)File對象
File file = new File(FileName);
// 創(chuàng)建FileOutputStream對應(yīng)FileWriter:將字節(jié)流轉(zhuǎn)換為字符流,即寫入out1的數(shù)據(jù)會自動由字節(jié)轉(zhuǎn)換為字符。
FileWriter out1 = new FileWriter(file);
// 寫入10個(gè)漢字
out1.write("字節(jié)流轉(zhuǎn)為字符流示例");
// 向“文件中”寫入"0123456789"+換行符
out1.write("0123456789\n");

FileReader

// 新建文件“file.txt”對應(yīng)File對象
File file = new File(FileName);
FileReader in1 = new FileReader(file);

// 測試read(),從中讀取一個(gè)字符
char c1 = (char)in1.read();
System.out.println("c1="+c1);

// 測試skip(long byteCount),跳過4個(gè)字符
in1.skip(6);

BufferedReader,BufferedWriter

==注意:== 其實(shí)看過其他類的源碼后,真正理解了字節(jié),字符輸入輸出流的作用機(jī)制后,就很清楚了,其實(shí)BufferedReader比起其他輸入流沒任何優(yōu)勢,只是他的緩沖池大,所以它緩沖的數(shù)據(jù)是其他輸入流的n倍。也就為其他輸入流提供了緩沖作用

BufferedReader

BufferedReader,字符緩沖輸入流,作用是為其他輸入流提供緩沖功能。BufferedReader從其他字符輸入流中讀取數(shù)據(jù)內(nèi)容,緩沖各個(gè)字符,從而實(shí)現(xiàn)字符、數(shù)組和行的高效讀取。當(dāng)通過其read函數(shù)讀取不到BufferedRead流中的數(shù)據(jù)時(shí)會調(diào)用fill()函數(shù)讀取源輸入流的數(shù)據(jù)來填充BufferedReader緩沖池的數(shù)據(jù)。

通常,Reader所作的每個(gè)讀取請求都會導(dǎo)致對底層字符或字節(jié)流進(jìn)行相應(yīng)的讀取請求。因此,建議用BufferedReader包裝所有其read()操作可能開銷很高的Reader(如FileReader和InputStreamReader)。例如,
BufferedReader in = new BufferedReader(new FileReader("foo.in"));將緩沖指定文件的輸入。如果沒有緩沖,則每次調(diào)用read()或readLine()都會導(dǎo)致從文件中讀取字節(jié),并將其轉(zhuǎn)換為字符后返回,而這是極其低效的。而BufferedReader就可以一次性幫我們讀更多,從而減少了讀的次數(shù),也就減少了字符轉(zhuǎn)化的次數(shù)。

// BufferedInputStream
private void fill() throws IOException {
    // ...
    int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
    // ...
}

問題:提供緩沖為什么能實(shí)現(xiàn)字符、數(shù)組和行的高效讀取?

  1. 是提高了讀取的效率
  2. 是減少了打開存儲介質(zhì)的連接次數(shù)。緩沖中的數(shù)據(jù)實(shí)際上是保存在內(nèi)存中,而原始數(shù)據(jù)可能是保存在硬盤中。從內(nèi)存中讀取數(shù)據(jù)的速度比從硬盤讀取數(shù)據(jù)的速度至少快10倍以上。

問題:為什么不一次性將Reader中全部數(shù)據(jù)都讀取到緩沖中呢?

  1. 讀取全部的數(shù)據(jù)所需要的時(shí)間可能會很長。
  2. 內(nèi)存價(jià)格很貴,容量遠(yuǎn)沒有硬盤那么大。我們能做的就是在效率和成本之間找到平衡點(diǎn)。大多數(shù)情況下,默認(rèn)值就足夠大了。
public class BufferedReader extends Reader {

    // 底層字符輸入流,BufferedReader實(shí)際操作的字符輸入流
    private Reader in;
    // 字符緩沖區(qū)
    private char cb[];
    // nChars是cb字符緩沖區(qū)中字符的總的個(gè)數(shù)
    // nextChar是下一個(gè)要讀取的字符在cb緩沖區(qū)中的位置
    private int nChars, nextChar;
    // 表示標(biāo)記無效。設(shè)置了標(biāo)記,但是被標(biāo)記位置由于某種原因?qū)е聵?biāo)記無效
    private static final int INVALIDATED = -2;
    //表示沒有標(biāo)記
    private static final int UNMARKED = -1;
    //標(biāo)記位置初始化為UNMARKED
    private int markedChar = UNMARKED;
    //在仍保留該標(biāo)記的情況下,對可讀取字符數(shù)量的限制。
    //在讀取達(dá)到或超過此限制的字符后,嘗試重置流可能會失敗。
    //限制值大于輸入緩沖區(qū)的大小將導(dǎo)致分配一個(gè)新緩沖區(qū),其大小不小于該限制值。因此應(yīng)該小心使用較大的值。
    private int readAheadLimit = 0; /* Valid only when markedChar > 0 */
    //表示是否跳過換行符。(skipLF ,skip line feed)
    private boolean skipLF = false;
    //表示當(dāng)做了標(biāo)記時(shí),是否忽略換行符。
    private boolean markedSkipLF = false;
    //字符緩沖區(qū)默認(rèn)大小
    private static int defaultCharBufferSize = 8192;
    //每行默認(rèn)的字符個(gè)數(shù)
    private static int defaultExpectedLineLength = 80;

    /**
     * 創(chuàng)建指定底層字符輸入流in和指定字符緩沖區(qū)大小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;
    }

    /**
     * 創(chuàng)建指定底層字符輸入流in和默認(rèn)字符緩沖區(qū)大小defaultCharBufferSize的BufferedReader
     */
    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);
    }
}

fill,read

/**
     * 填充緩沖區(qū)。
     * 如果標(biāo)記有效,要考慮標(biāo)記。
     */
    private void fill() throws IOException {
        //cb中填充數(shù)據(jù)的起始位置,記錄了此次填充時(shí)是從緩沖池的什么位置開始填充數(shù)據(jù)的
        int dst;
        //如果沒有標(biāo)記,從緩沖區(qū)索引為0的位置開始填充
        //這個(gè)if的處理邏輯是填充緩沖池前的原數(shù)據(jù)加載行為,
        //即找到緩沖池中原本標(biāo)記的有效數(shù)據(jù),讀取readAheadLimit個(gè)后,舍棄其他數(shù)據(jù)。
        if (markedChar <= UNMARKED) {
            dst = 0;
        } else {//如果有標(biāo)記
            //delta為標(biāo)記位置與下個(gè)讀取字符之間的距離
            int delta = nextChar - markedChar;
            //如果delta超出readAheadLimit,標(biāo)記即為無效。
            if (delta >= readAheadLimit) { 
                markedChar = INVALIDATED;
                readAheadLimit = 0;
                dst = 0;
            } else {
                //如果delta沒有超出readAheadLimit,即標(biāo)記有效
                //且readAheadLimit小于等于緩沖區(qū)長度
                //將markedChar與nextChar之間的字符寫入到緩沖區(qū)中
                if (readAheadLimit <= cb.length) {
                    /* Shuffle in the current buffer */
                    System.arraycopy(cb, markedChar, cb, 0, delta);
                    markedChar = 0;
                    dst = delta;
                } else {
                    //如果delta沒有超出readAheadLimit,即標(biāo)記有效
                    //且readAheadLimit大于緩沖區(qū)長度
                    //將重新設(shè)置緩沖區(qū)大小,markedChar與nextChar之間的字符寫入到緩沖區(qū)中
                    char ncb[] = new char[readAheadLimit];
                    //這里觸發(fā)的條件是緩沖區(qū)已經(jīng)標(biāo)記,且標(biāo)記后讀取的規(guī)定的字符數(shù)要超過計(jì)算的個(gè)數(shù)(nextChar - markedChar),
                    //且要讀取的個(gè)數(shù)超過了緩沖區(qū)總長度。
                    System.arraycopy(cb, markedChar, ncb, 0, delta);
                    cb = ncb;
                    markedChar = 0;
                    dst = delta;
                }
                nextChar = nChars = delta;
           }
    }

    int n;
    //從底層輸入流中讀取數(shù)據(jù),并存儲到緩沖區(qū)cb中
    //如果沒有讀取到數(shù)據(jù),就繼續(xù)讀(可能有數(shù)據(jù)為空的情況),直到讀到數(shù)據(jù)或者到達(dá)流末尾為止(只要不返回-1,就說明沒結(jié)束)
    do {
        n = in.read(cb, dst, cb.length - dst);
    } while (n == 0);
    //如果讀到了數(shù)據(jù)
    if (n > 0) {
        nChars = dst + n;
        nextChar = dst;
    }
}

/**
 * 讀取單個(gè)字符。
 *
 * @return 作為一個(gè)整數(shù)(其范圍從0到65535( 0x00-0xffff))返回,如果已到達(dá)流末尾,則返回 -1
 */
public int read() throws IOException {
    synchronized (lock) {
        //確認(rèn)BufferedReader是否處于開啟狀態(tài)
        ensureOpen();
        //???什么意義?
        for (;;) {
            //如果緩沖區(qū)數(shù)據(jù)已被讀完,填充緩沖區(qū)。如果填充緩沖區(qū)后緩沖區(qū)依然是空的,說明已到達(dá)流末尾,返回-1。
            if (nextChar >= nChars) {
                fill();
                if (nextChar >= nChars)
                    return -1;
            }
            //如果skipLF為true,說明要跳過換行符
            if (skipLF) {
                //說明只作用一次
                skipLF = false;
                //如果緩沖區(qū)內(nèi)下個(gè)字符為換行符,跳過它。
                if (cb[nextChar] == '\n') {
                    nextChar++;
                    continue;
                }
            }
            //返回緩沖區(qū)中下個(gè)字符,然后nextChar+1
            return cb[nextChar++];
        }
    }
}

/**
 * 從緩沖區(qū)中讀取數(shù)據(jù),寫入到cbuf中。off為開始存儲字符處的偏移量。len為要讀取的最大字符數(shù)。
 * 如有必要,從底層輸入流中讀取數(shù)據(jù)。
 * 
 * 該方法在read(char cbuf[], int off, int len)中被調(diào)用
 * 
 * @param   b     目標(biāo)字符數(shù)組
 * @param   off   開始存儲字符處的偏移量
 * @param   len   要讀取的最大字符數(shù)
 * @return  實(shí)際讀入cbuf的總字節(jié)數(shù),如果由于已到達(dá)流末尾而不再有數(shù)據(jù),則返回-1。
 */
private int read1(char[] cbuf, int off, int len) throws IOException {
    //如果緩沖區(qū)已被讀完
    if (nextChar >= nChars) {
        //如果要讀取的長度大于等于緩沖區(qū)大小,且沒有標(biāo)記,且不跳過換行符
        if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
            //直接從底層輸入流中讀取數(shù)據(jù)到cbuf中,
            //???這里有個(gè)搞不懂的地方,那這樣的話那么緩沖區(qū)的暫存數(shù)據(jù)還未被讀走不是選擇性[跳]著讀了嗎?
            return in.read(cbuf, off, len);
        }
        //填充緩沖區(qū)
        fill();
    }
    //如果以上步驟執(zhí)行完后,緩沖區(qū)還是沒有可讀數(shù)據(jù),說明已經(jīng)到達(dá)流末尾,返回-1
    if (nextChar >= nChars) return -1;
    //如果跳過換行符
    if (skipLF) {
        //說明換行符只不被讀一次
        skipLF = false;
        //如果下個(gè)字符是換行符
        if (cb[nextChar] == '\n') {
            //跳過它
            nextChar++;
            //如果緩沖區(qū)已被讀完,填充緩沖區(qū)
            if (nextChar >= nChars)
                fill();
            //如果填充緩沖區(qū)后,緩沖區(qū)依然是空的。說明已經(jīng)到達(dá)流末尾,返回-1
            if (nextChar >= nChars)
                return -1;
        }
    }
    // 如果緩沖池中還有數(shù)據(jù)的話也就不填了,該次讀取僅讀有的這些
    // 所以說,如果一個(gè)數(shù)組過來讀沒讀滿的話,不能說明原數(shù)據(jù)沒數(shù)據(jù)了。
    int n = Math.min(len, nChars - nextChar);
    //讀取
    System.arraycopy(cb, nextChar, cbuf, off, n);
    //緩沖區(qū)當(dāng)前位置+n
    nextChar += n;
    return n;
}

==注意== : 這里帶上read1函數(shù)的一個(gè)主要原因是源碼中的設(shè)計(jì)有個(gè)地方不太明白。注釋中已經(jīng)說明

其他地方無論readLine讀行還是skip跳字符等,都和之前研究過的BufferedInputStream一樣,不一樣的只是多了關(guān)于換行符的判斷。

BufferedWriter

BufferedWriter,字符緩沖輸出流,作用是為其他輸出流提供緩沖功能。BufferedWriter將文本寫入其他字符輸出流,緩沖各個(gè)字符,從而提供單個(gè)字符、數(shù)組和字符串的高效寫入。

通常Writer將其輸出立即發(fā)送到底層字符或字節(jié)流。除非要求提示輸出,否則建議用BufferedWriter包裝所有其write()操作可能開銷很高的 Writer(如FileWriters和OutputStreamWriters)。例如,PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("foo.out"))); 將緩沖PrintWriter對文件的輸出。如果沒有緩沖,則每次調(diào)用print()方法會導(dǎo)致將字符轉(zhuǎn)換為字節(jié),然后立即寫入到文件,而這是極其低效的。

// BufferedOutputStream
private void flushBuffer() throws IOException {
    if (count > 0) {
        out.write(buf, 0, count);// 一次性向OutPutStream輸出更多數(shù)據(jù)
        count = 0;
    }
}

BufferedWriter是如何為其他輸出流提供緩沖功能的

創(chuàng)建BufferedWriter時(shí),我們會通過它的構(gòu)造函數(shù)指定某個(gè)底層字符輸出流Writer為參數(shù)。當(dāng)程序中每次將單個(gè)字符、數(shù)組和字符串寫入到BufferedWriter中時(shí)、都會檢查BufferedWriter中的緩存區(qū)是否存滿,如果沒有存滿則將字符寫入到緩存區(qū)中;如果存滿,則調(diào)用底層的writer(char[] b, int off, int len)將緩存區(qū)中的所有字符一次性寫入到底層Writer中。

提供緩沖為什么能實(shí)現(xiàn)單個(gè)字符、數(shù)組和字符串的高效寫入

如果沒有緩沖,使用底層字符輸出流向目的文件中寫入單個(gè)字符、數(shù)組和字符串時(shí),每寫入一次就要打開一次到目的文件的連接。這樣頻繁的訪問效率非常底下。比如,將一個(gè)非常大的數(shù)據(jù)寫入到目的文件中,如果每寫入一個(gè)字符就要打開一次到目的文件的連接,可以想象效率是如何低下。

有了緩沖,當(dāng)使用底層字符輸出流向目的文件中寫入單個(gè)字符、數(shù)組和字符串時(shí),將單個(gè)字符、數(shù)組和字符串先寫入到BufferedWriter的內(nèi)置緩存空間中,然后當(dāng)達(dá)到一定數(shù)量時(shí)一次性寫入Writer流中。此時(shí),Writer就可以打開一次通道,將這個(gè)數(shù)據(jù)塊寫入到文件中。這樣雖然不能達(dá)到一次訪問就將所有數(shù)據(jù)寫入磁盤中的效果,但也大大提高了效率和減少了對磁盤的訪問量。

write,newLine

/**
 * 寫入字符數(shù)組的某一部分。
 * 將字符數(shù)組的cbuf中從下標(biāo)off開始的len個(gè)字符寫入緩沖區(qū)cb中
 */
public void write(char cbuf[], int off, int len) throws IOException {
    synchronized (lock) {
        ensureOpen();
        //檢查參數(shù)是否合法
        if ((off < 0) || (off > cbuf.length) || (len < 0) ||
            ((off + len) > cbuf.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }

        //如果cbuf的數(shù)據(jù)的長度大于等于緩沖區(qū)長度
        if (len >= nChars) {
            //刷新緩沖區(qū),將要寫入的數(shù)據(jù)直接寫入到底層輸出流中
            flushBuffer();
            //Writer抽象類的不同子類實(shí)現(xiàn)不同,因?yàn)樵摵瘮?shù)在Writer抽象類中聲明的是抽象方法,也就是準(zhǔn)備給子類各自去不同實(shí)現(xiàn)的
            //CharArrayWriter的實(shí)現(xiàn)方式是如果緩沖池大小小于len則擴(kuò)容成(緩沖池大小1倍,len大小)兩者較大的量。
            //PipedWriter的實(shí)現(xiàn)方式是迭代len次的調(diào)用write(int a),逐個(gè)寫入
            out.write(cbuf, off, len);
            return;
        }

        //如果cbuf的數(shù)據(jù)的長度小于緩沖區(qū)長度,將數(shù)據(jù)寫入緩沖區(qū)中。
        int b = off, t = off + len;
        while (b < t) {
            int d = min(nChars - nextChar, t - b);
            System.arraycopy(cbuf, b, cb, nextChar, d);
            b += d;
            nextChar += d;
            //如果寫入過程中緩沖區(qū)滿了,刷新緩沖區(qū),繼續(xù)將數(shù)據(jù)寫入緩沖區(qū)。
            if (nextChar >= nChars)
                flushBuffer();
        }
    }
    
    /**
     * 寫入一個(gè)行分隔符。
     * 行分隔符字符串由系統(tǒng)屬性line.separator定義,并且不一定是單個(gè)新行('\n')符。
     */
    public void newLine() throws IOException {
        // 這里寫入一個(gè)行分隔符,所以當(dāng)你再寫數(shù)據(jù)的時(shí)候,如果操作的是文件
        // 新數(shù)據(jù)就會出現(xiàn)在文件另一行。
        write(lineSeparator);
    }

}

這里寫上write函數(shù)的原因是方法中out.write(cbuf, off, len);這句曾困擾我一會兒,突然想不通當(dāng)len >= nChars時(shí)具體他會怎么操作了。

后來想明白了,還操作個(gè)啥,BufferedWriter才不會管,讓實(shí)際操作的***Writer輸入流自己去處理去,因?yàn)楸旧碓摲椒ㄔ诔橄蟾割怶riter中就是抽象函數(shù),也就是任意Writer的非抽象子類都必須重寫。所以它根本必須要管,也沒權(quán)利管。

總結(jié)

  • BufferedReader,字符緩沖輸入流,作用是為其他輸入流提供緩沖功能。BufferedReader從其他字符輸入流中讀取文本,緩沖各個(gè)字符,從而實(shí)現(xiàn)字符、數(shù)組和行的高效讀取。
  • BufferedReader支持標(biāo)記功能。
  • BufferedWriter,字符緩沖輸出流,作用是為其他輸出流提供緩沖功能。BufferedWriter將文本寫入其他字符輸出流,緩沖各個(gè)字符,從而提供單個(gè)字符、數(shù)組和字符串的高效寫入。

PrintWriter

PrintWriter 是字符類型的打印輸出流,它繼承于Writer。

PrintStream 用于向文本輸出流打印對象的格式化表示形式。它實(shí)現(xiàn)在 PrintStream 中的所有 print 方法。它不包含用于寫入原始字節(jié)的方法,對于這些字節(jié),程序應(yīng)該使用未編碼的字節(jié)流進(jìn)行寫入。

==需要注意的是==,該類和其對應(yīng)的字節(jié)輸出流的自動刷新功能不同,它只有在調(diào)用printf,及println函數(shù)的時(shí)候才會自動刷新。這一點(diǎn)下面的源碼注釋中我有寫。

PrintWriter

public class PrintWriter extends Writer {

    /**
     * PrintWriter的底層字符輸出流
     */
    protected Writer out;
    //是否自動刷新。
    //如果為true,每次執(zhí)行printf()[format函數(shù)的原因], 
    // println()[newLine函數(shù)的原因]函數(shù),都會調(diào)用flush()函數(shù)。
    private final boolean autoFlush;
    //是否有異常
    //當(dāng)PrintWriter有異常產(chǎn)生時(shí),會被本身捕獲,并設(shè)置trouble為true
    private boolean trouble = false;
    //用于格式化字符串的對象
    private Formatter formatter;
    //字節(jié)打印流
    //用于checkError方法
    private PrintStream psOut = null;

    /**
     * 行分隔符
     * 在PrintWriter被創(chuàng)建時(shí)line.separator屬性的值。
     */
    private final String lineSeparator;

    /**
     * 返回csn(字符集名字)對應(yīng)的Chaset
     * csn為null或是不支持的字符集,拋出異常
     */
    private static Charset toCharset(String csn)
        throws UnsupportedEncodingException
    {
        Objects.requireNonNull(csn, "charsetName");
        try {
            return Charset.forName(csn);
        } catch (IllegalCharsetNameException|UnsupportedCharsetException unused) {
            // UnsupportedEncodingException should be thrown
            throw new UnsupportedEncodingException(csn);
        }
    }

    /**
     * 創(chuàng)建新的PrintWriter。
     * 指定底層輸出流,默認(rèn)不會自動flush,采用默認(rèn)字符集
     */
    public PrintWriter (Writer out) {
        this(out, false);
    }

    /**
     * 創(chuàng)建新的PrintWriter。
     * 指定底層輸出流,指定是否自動flush,采用默認(rèn)字符集
     */
    public PrintWriter(Writer out,
                       boolean autoFlush) {
        super(out);
        this.out = out;
        this.autoFlush = autoFlush;
        //line.separator屬性的值
        lineSeparator = java.security.AccessController.doPrivileged(
            new sun.security.action.GetPropertyAction("line.separator"));
    }

    /**
     * 創(chuàng)建新的PrintWriter。
     * 指定底層輸出流,不自動flush,采用默認(rèn)字符集
     */
    public PrintWriter(OutputStream out) {
        this(out, false);
    }

    /**
     * 創(chuàng)建新的PrintWriter。
     * 指定底層輸出流,指定是否自動flush,采用默認(rèn)字符集
     */
    public PrintWriter(OutputStream out, boolean autoFlush) {
        this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush);

        // save print stream for error propagation
        if (out instanceof java.io.PrintStream) {
            psOut = (PrintStream) out;
        }
    }

    /**
     * 創(chuàng)建新的PrintWriter。
     * 指定文件名,默認(rèn)不自動flush,采用默認(rèn)字符集
     */
    public PrintWriter(String fileName) throws FileNotFoundException {
        this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))),
             false);
    }

    /**
     * 私有構(gòu)造方法。創(chuàng)建新的PrintWriter。
     * 指定文件名,默認(rèn)不自動flush,采用指定字符集
     */
    private PrintWriter(Charset charset, File file)
        throws FileNotFoundException
    {
        this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), charset)),
             false);
    }

    /**
     * 創(chuàng)建新的PrintWriter。
     * 指定文件名,默認(rèn)不自動flush,采用指定字符集
     */
    public PrintWriter(String fileName, String csn)
        throws FileNotFoundException, UnsupportedEncodingException
    {
        this(toCharset(csn), new File(fileName));
    }

    /**
     * 創(chuàng)建新的PrintWriter。
     * 指定文件名,默認(rèn)不自動flush,采用默認(rèn)字符集
     */
    public PrintWriter(File file) throws FileNotFoundException {
        this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))),
             false);
    }

    /**
     * 創(chuàng)建新的PrintWriter。
     * 指定文件名,默認(rèn)不自動flush,采用指定字符集
     */
    public PrintWriter(File file, String csn)
        throws FileNotFoundException, UnsupportedEncodingException
    {
        this(toCharset(csn), file);
    }
}

光構(gòu)造函數(shù)就有9個(gè),其實(shí)底層用的就只有一個(gè)。

public PrintWriter(Writer out,boolean autoFlush) {
    super(out);
    this.out = out;
    this.autoFlush = autoFlush;
    lineSeparator = java.security.AccessController.doPrivileged(
        new sun.security.action.GetPropertyAction("line.separator"));
}

checkError,setError,clearError

該類除構(gòu)造函數(shù)會拋出異常外,其余所有的函數(shù)調(diào)用均不會拋出異常。在調(diào)用時(shí)如果拋出異常會被catch掉,然后設(shè)置trouble變量為true,并不會顯示拋出。

/**
 * 如果流沒有關(guān)閉,則刷新流且檢查其錯(cuò)誤狀態(tài)。
 */
public boolean checkError() {
    //如果流沒有關(guān)閉,則刷新流
    if (out != null) {
        flush();
    }
    //檢查錯(cuò)誤狀態(tài)
    if (out instanceof java.io.PrintWriter) {
        PrintWriter pw = (PrintWriter) out;
        return pw.checkError();
    } else if (psOut != null) {
        return psOut.checkError();
    }
    //如果拋出了異常,返回true
    return trouble;
}

/**
 * 指示已發(fā)生錯(cuò)誤。
 * 在調(diào)用clearError()之前,此方法將導(dǎo)致checkError()的后續(xù)調(diào)用返回 true。
 */
protected void setError() {
    trouble = true;
}

/**
 * 清除此流的錯(cuò)誤狀態(tài)。
 */
protected void clearError() {
    trouble = false;
}

write

/**
 * 寫入字符數(shù)組。
 * 此方法不能從Writer類繼承,因?yàn)樗仨毴∠鸌/O異常。
 */
public void write(char buf[]) {
    write(buf, 0, buf.length);
}

很多時(shí)候我們不得不重新自己定義一些函數(shù)。因?yàn)槲覀冇行┰虿荒苤貙懜割惖暮瘮?shù)

其實(shí)大多數(shù)函數(shù)都是直接底層調(diào)用,除了會有字符輸入輸出流特有的安全檢查機(jī)制ensureOpen對于底層實(shí)際操作的輸入輸出流的null判斷外。大部分都是直接調(diào)用,然后區(qū)別只是catch到異常的時(shí)候不再往外拋,而是設(shè)置trouble變量為true。如下:

/**
 * 寫入字符串的某一部分。
 */
public void write(String s, int off, int len) {
    try {
        synchronized (lock) {
            ensureOpen();
            out.write(s, off, len);
        }
    }
    catch (InterruptedIOException x) {
        Thread.currentThread().interrupt();
    }
    catch (IOException x) {
        trouble = true;
    }
}

printf

// 其實(shí)底層調(diào)用的就是Formatter類的format方法
public PrintWriter printf(String format, Object ... args) {
    return format(format, args);
}

總結(jié)

  • 從構(gòu)造方法中可以看到,BufferedWriter包裝了底層輸出流,為其提供了緩沖功能。
  • 此類中的方法不會拋出I/O異常,盡管其某些構(gòu)造方法可能拋出異常。客戶端可能會查詢調(diào)用checkError()是否出現(xiàn)錯(cuò)誤。
  • print方法可以打印boolean、char 、char[]、double 、float、int、 long、Object、String這些類型。都是按照平臺的默認(rèn)字符串編碼將String.valueOf() 方法生成的字符串轉(zhuǎn)換為字節(jié),并完全以write(int)方法的方式向輸出流中寫入這些字節(jié)。
  • println(type param)方法可以打印boolean、char 、char[]、double 、float、int、 long、Object、String這些類型。都是先調(diào)用print方法打印,再調(diào)用println()方法換行。
  • printf方法和format方法的效果是相同的。因?yàn)閜rintf方法是依賴于調(diào)用format方法實(shí)現(xiàn)的。
  • append方法其實(shí)是依賴于out.write方法實(shí)現(xiàn)的。

RandomAccessFile

  • RandomAccessFile 是隨機(jī)訪問文件 (包括讀/寫)的類。它支持對文件隨機(jī)訪問的讀取和寫入,即我們可以從指定的位置讀取/寫入文件數(shù)據(jù)。
  • 需要注意的是,RandomAccessFile 雖然屬于java.io包,但它不是InputStream或者OutputStream的子類;它也不同于FileInputStream和FileOutputStream。 FileInputStream 只能對文件進(jìn)行讀操作,而FileOutputStream 只能對文件進(jìn)行寫操作;但是,RandomAccessFile 同時(shí)支持文件的讀和寫,并且它支持隨機(jī)訪問。

RandomAccessFile 模式說明

RandomAccessFile共有4種模式:"r", "rw", "rws"和"rwd"。

"r"    以只讀方式打開。調(diào)用結(jié)果對象的任何 write 方法都將導(dǎo)致拋出 IOException。  
"rw"   打開以便讀取和寫入。
"rws"  打開以便讀取和寫入。相對于 "rw","rws" 還要求對“文件的內(nèi)容”或“元數(shù)據(jù)”的每個(gè)更新都同步寫入到基礎(chǔ)存儲設(shè)備。 這里的s是synchronized的意思
"rwd"  打開以便讀取和寫入,相對于 "rw","rwd" 還要求對“文件的內(nèi)容”的每個(gè)更新都同步寫入到基礎(chǔ)存儲設(shè)備。  和rws的區(qū)別是不包含原數(shù)據(jù)

==說明:==

  1. 什么是“元數(shù)據(jù)”,即metadata?

metadata是“關(guān)于數(shù)據(jù)的數(shù)據(jù)”。在文件系統(tǒng)中,數(shù)據(jù)被包含在文件和文件夾中;metadata信息包括:“數(shù)據(jù)是一個(gè)文件,一個(gè)目錄還是一個(gè)鏈接”,“數(shù)據(jù)的創(chuàng)建時(shí)間(簡稱ctime)”,“最后一次修改時(shí)間(簡稱mtime)”,“數(shù)據(jù)擁有者”,“數(shù)據(jù)擁有群組”,“訪問權(quán)限”等等。也就是數(shù)據(jù)的修飾信息,狀態(tài)信息。而非具體的數(shù)據(jù)內(nèi)容

  1. "rw", "rws", "rwd" 的區(qū)別?
  • 當(dāng)操作的文件是存儲在本地的基礎(chǔ)存儲設(shè)備上時(shí)(如硬盤, NandFlash等),"rws" 或 "rwd", "rw" 才有區(qū)別。
  • 當(dāng)模式是 "rws" 并且 操作的是基礎(chǔ)存儲設(shè)備上的文件;那么,每次“更改文件內(nèi)容[如write()寫入數(shù)據(jù)]” 或 “修改文件元數(shù)據(jù)(如文件的mtime)”時(shí),都會將這些改變同步到基礎(chǔ)存儲設(shè)備上
  • 當(dāng)模式是 "rwd" 并且 操作的是基礎(chǔ)存儲設(shè)備上的文件;那么,每次“更改文件內(nèi)容[如write()寫入數(shù)據(jù)]”時(shí),都會將這些改變同步到基礎(chǔ)存儲設(shè)備上。
  • 當(dāng)模式是 "rw" 并且 操作的是基礎(chǔ)存儲設(shè)備上的文件;那么,關(guān)閉文件時(shí),會將“文件內(nèi)容的修改”同步到基礎(chǔ)存儲設(shè)備上。至于,“更改文件內(nèi)容”時(shí),是否會立即同步,取決于系統(tǒng)底層實(shí)現(xiàn)。

作者的演示程序

還是很好理解的。

/**
* RandomAccessFile 測試程序
*
* 運(yùn)行結(jié)果(輸出如下):
* c1=a
* c2=b
* buf=9876543210
* 
* 此外,
* (01) 在源文件所在目錄生成了file.txt。
* (02) 注意RandomAccessFile寫入boolean, byte, char, int,所占的字符個(gè)數(shù)。
*
* @author skywang
*/
public class RandomAccessFileTest {

   private static final String FileName = "file.txt";

   public static void main(String[] args) {
       // 若文件“file.txt”存在,則刪除該文件。
       File file = new File(FileName);
       if (file.exists())
           file.delete();

       testCreateWrite();
       testAppendWrite();
       testRead();
   }

   /**
    * 若“file.txt”不存在的話,則新建文件,并向文件中寫入內(nèi)容
    */
   private static void testCreateWrite() {
       try {
           // 創(chuàng)建文件“file.txt”對應(yīng)File對象
           File file = new File(FileName);
           // 創(chuàng)建文件“file.txt”對應(yīng)的RandomAccessFile對象
           RandomAccessFile raf = new RandomAccessFile(file, "rw");

           // 向“文件中”寫入26個(gè)字母+回車
           raf.writeChars("abcdefghijklmnopqrstuvwxyz\n");
           // 向“文件中”寫入"9876543210"+回車
           raf.writeChars("9876543210\n");

           raf.close();
       } catch(IOException e) {
           e.printStackTrace();
       }
   }

   /**
    * 向文件末尾追加內(nèi)容
    */
   private static void testAppendWrite() {
       try {
           // 創(chuàng)建文件“file.txt”對應(yīng)File對象
           File file = new File(FileName);
           // 創(chuàng)建文件“file.txt”對應(yīng)的RandomAccessFile對象
           RandomAccessFile raf = new RandomAccessFile(file, "rw");

           // 獲取文件長度
           long fileLen = raf.length();
           // 將位置定位到“文件末尾”
           raf.seek(fileLen);

           // 以下向raf文件中寫數(shù)據(jù)  
           raf.writeBoolean(true); // 占1個(gè)字節(jié)  
           raf.writeByte(0x41);    // 占1個(gè)字節(jié)  
           raf.writeChar('a');     // 占2個(gè)字節(jié)  
           raf.writeShort(0x3c3c); // 占2個(gè)字節(jié)  
           raf.writeInt(0x75);     // 占4個(gè)字節(jié)  
           raf.writeLong(0x1234567890123456L); // 占8個(gè)字節(jié)  
           raf.writeFloat(4.7f);  // 占4個(gè)字節(jié)  
           raf.writeDouble(8.256);// 占8個(gè)字節(jié)  
           raf.writeUTF("UTF嚴(yán)"); // UTF-8格式寫入
           raf.writeChar('\n');   // 占2個(gè)字符。“換行符”

           raf.close();
       } catch(IOException e) {
           e.printStackTrace();
       }
   }

   /**
    * 通過RandomAccessFile讀取文件
    */
   private static void testRead() {
       try {
           // 創(chuàng)建文件“file.txt”對應(yīng)File對象
           File file = new File(FileName);
           // 創(chuàng)建文件“file.txt”對應(yīng)的RandomAccessFile對象,以只讀方式打開
           RandomAccessFile raf = new RandomAccessFile(file, "r");

           // 讀取一個(gè)字符
           char c1 = raf.readChar();
           System.out.println("c1="+c1);
           // 讀取一個(gè)字符
           char c2 = raf.readChar();
           System.out.println("c2="+c2);

           // 跳過54個(gè)字節(jié)。
           raf.seek(54);

           // 測試read(byte[] buffer, int byteOffset, int byteCount)
           byte[] buf = new byte[20];
           raf.read(buf, 0, buf.length);
           System.out.println("buf="+(new String(buf)));

           raf.close();
       } catch(IOException e) {
           e.printStackTrace();
       }
   }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容