- 說明
- PipedReader,PipedWriter
- InputStreamReader,OutputStreamWriter
- FileReader,F(xiàn)ileWriter
- BufferedReader,BufferedWriter
- PrintWriter
- RandomAccessFile
說明
整個(gè)系列的文章全部參考或直接照搬下面兩位作者的文章,這里只是根據(jù)自己需要對原作者的文章梳理的總結(jié),僅給自己日后復(fù)習(xí)時(shí)提供思路,如有讀者看到學(xué)習(xí)時(shí)建議移步原作。再次重申并非我所寫
- 潘威威:Java8 I/O源碼-目錄
- skywang12345:Java I/O系列
另兩篇本人總結(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ù)組和行的高效讀取?
- 是提高了讀取的效率
- 是減少了打開存儲介質(zhì)的連接次數(shù)。緩沖中的數(shù)據(jù)實(shí)際上是保存在內(nèi)存中,而原始數(shù)據(jù)可能是保存在硬盤中。從內(nèi)存中讀取數(shù)據(jù)的速度比從硬盤讀取數(shù)據(jù)的速度至少快10倍以上。
問題:為什么不一次性將Reader中全部數(shù)據(jù)都讀取到緩沖中呢?
- 讀取全部的數(shù)據(jù)所需要的時(shí)間可能會很長。
- 內(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ù)
==說明:==
- 什么是“元數(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)容
- "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();
}
}
}