Java IO流
在Java IO中,流是一個核心的概念。流從概念上來說是一個連續的數據流。你既可以從流中讀取數據,也可以往流中寫數據。流與數據源或者數據流向的媒介相關聯。在Java IO中流既可以是字節流
(以字節爲單位進行讀寫),也可以是字符流
(以字符爲單位進行讀寫)。
一個程序需要InputStream或者Reader從數據源讀取數據,需要OutputStream或者Writer將數據寫入到目標媒介中。InputStream和Reader與數據源相關聯,OutputStream和writer與目標媒介相關聯
。
Java IO的用途和特徵
Java IO中包含了許多InputStream、OutputStream、Reader、Writer的子類。這樣設計的原因是讓每一個類都負責不同的功能。這也就是爲什麼IO包中有這麼多不同的類的緣故。各類用途彙總如下:
- 文件訪問
- 網絡訪問
- 內存緩存訪問
- 線程內部通信(管道)
- 緩衝
- 過濾
- 解析
- 讀寫文本 (Readers / Writers)
- 讀寫基本類型數據 (long, int etc.)
- 讀寫對象
Java IO類概述表
通過輸入、輸出、基於字節或者字符、以及其他比如緩衝、解析之類的特定用途劃分的大部分Java IO類的表格。
字節流
簡介
字節流是以面向字節
的形式提供IO操作。
-
字節流(OutputStream/InputStream)處理單元爲1個字節,操作
字節
和字節數組
。 -
字節流可用於任何類型。
-
字節流在操作的時候本身是不會用到緩衝區的,是與文件本身直接操作的,所以字節流在操作文件時,即使不關閉資源,文件也能輸出。
基類字節流
InputStream/OutputStream 是Java IO API中所有輸入/輸出
字節流
的父類。表示有序的字節流,換句話說,可以將 InputStream/OutputStream 中的數據作爲有序的字節序列讀寫。該類是一個抽象類,我們一般很少直接使用它們,而是使用其子類實現我們對字節的讀寫操作。
InPutStream
所有輸入字節流的父類
-
重要方法
read()
從輸入流中讀取單個字節,是個抽象類,由子類實現
public abstract int read()
read(byte b[])
從輸入流中讀取指定長度(b.length)的字節轉換爲字節數組(存儲在數組中)(內部調用read(byte b[], int off, int len)方法)
public int read(byte b[]) throws IOException { return read(b, 0, b.length); }
read(byte b[], int off, int len)
從輸入流中讀取len長度的字節數據,存儲在數組b中偏移off位置.(內部通過循環從輸入流中逐個字節讀取存儲在數據中)
public int read(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); if (c == -1) { return -1; } b[off] = (byte)c; int i = 1; try { for (; i < len ; i++) { //循環需要讀取的字節長度,調用read方法逐個字節讀取 c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } return i; }
OutputStream
所有輸出字節流的父類
-
重要方法
write(int b)
將單個字節寫入此文件的輸出流
public abstract void write(int b) throws IOException;
write(byte b[])
將指定的字節數組寫入此文件的輸出流.(內部調用write(byte b[], int off, int len)方法)
public void write(byte b[]) throws IOException { write(b, 0, b.length); }
write(byte b[], int off, int len)
將指定的字節數組,從偏移off位置開始,len長度字節寫入此文件輸出流.(內部通過循環逐個字節將數組中數據寫入輸出流)
public void write(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } //循環寫入字節長度,調用write(int b)方法逐個將數組中字節寫入到輸出流 for (int i = 0 ; i < len ; i++) { write(b[off + i]); } }
文件字節流
FileInputStream
FileInputStream被稱爲文件字節輸入流,意思指對文件數據以
字節
的形式進行讀取操作如讀取圖片視頻等。其繼承於InputStream,擁有輸入流的基本特性。
-
構造方法
FileInputStream(String name)
通過一個文件路徑來創建一個FileInputStream文件輸入流如果文件不存在,或者文件是一個目錄,而不是常規文件,或由於其他原因無法打開,則拋出FileNotFoundException異常!(內部調用FileInputStream(File file)方法)
public FileInputStream(String name) throws FileNotFoundException { this(name != null ? new File(name) : null); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; FileInputStream fis = new FileInputStream(filePath);
FileInputStream(File file)
通過指定一個實際文件,來創建一個FileInputStream文件輸入流。
/** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; File file = new File(filepath); if(!file.exists()){ file.createNewFile(); } FileInputStream fis = new FileInputStream(file);
-
重要方法
read()
從輸入流中每次讀取一個字節
public int read() throws IOException { byte[] b = new byte[1]; if(read(b, 0, 1) != -1)){ return b[0] & 0xff; }else{ return -1; } }
read(byte b[])
從輸入流中讀取b.length長度字節轉換爲字節數組.(內部調用了read(byte b[], int off, int len)方法)
public int read(byte b[]) throws IOException { return read(b, 0, b.length); } /** *代碼示例 */ FileInputStream fis = new FileInputStrean(new File("/data/data/com.test/test.txt")); byte buffer[] = new byte[8*1024]; int len =- 1; while((le = fis.read(buffer)) != -1{ System.out.println("str = " + new String(buffer,0,len)) }
read(byte b[], int off, int len)
FileInputStream的read(byte b[],int off,int len)方法內部通過JNI實現(
IoBridge.read(fd, b, off, len)
),我們無法查看具體實現,但是我們可以參考其父類InputStream的read(byte b[],int off,int len)的方法如下/** *從輸入流中讀取最多len字節的數據到字節數組中b中。 *@param b:存儲讀取的字節數據 *@param off:讀取字節數據存儲在b中偏移量 *@param len:需要讀取的長度 *PS:內部通過循環調用read()逐個字節讀取存目標數組中 */ public int read(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); if (c == -1) { return -1; } b[off] = (byte)c; int i = 1; try { //遍歷目標的長度,調用read()方法,逐個字節讀取到目標字節數組內 for (; i < len ; i++) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } return i; } /** *代碼示例 */ FileInputStream fis = new FileInputStrean(new File("/data/data/com.test/test.txt")); byte buffer[] = new byte[8*1024]; int len =- 1; while((le = fis.read(buffer,0,buffer.length)) != -1{ System.out.println("str = " + new String(buffer,0,len)) }
FileOutputStream
FileOutputStream被稱爲文件字節輸出流,意思指對文件數據以字節的形式進行寫入操作。專用於輸出原始字節流如圖像數據等,其繼承OutputStream類,擁有輸出流的基本特性
-
構造方法
FileOutputStream(String name)
通過指定文件名稱創建FileOutputStream文件輸出流。(內部調用FileOutputStream(File file)方法)
public FileOutputStream(String name) throws FileNotFoundException { this(name != null ? new File(name) : null, false); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; FileOutputStream fos = new FileOutputStream(filePath);
FileOutputStream(String name, boolean append)(內部調用FileOutputStream(File file, boolean append)方法)
/** *通過指定文件名稱和append創建一個FileOutputStream文件輸出流。 *@param append:true-表示向文件尾部追加寫入數據;false-表示覆蓋文件內容,默認爲false */ public FileOutputStream(String name, boolean append) throws FileNotFoundException { this(name != null ? new File(name) : null, append); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; FileOutputStream fos = new FileOutputStream(filePath,true); fos.write(byte b[])//調用write方法時候會向目標文件尾部追加內容,而不是覆蓋原來內容。
FileOutputStream(File file)(內部調用FileOutputStream(File file, boolean append)方法)
/** *通過指定實例文件創建一個FileOutputStream文件輸出流。 */ public FileOutputStream(File file) throws FileNotFoundException { this(file, false); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; File file = new File(filepath); if(!file.exists()){ file.createNewFile(); } FileOutputStream fos = new FileOutputStream(file);
FileOutputStream(File file, boolean append)(以上構造方法最終都是通過該方法實現)
/** *通過指定的實例文件和append創建一個FileOutputStream文件輸出流。 * 1)如果文件不存在,則自動創建; * 2)如果文件是一個目錄,而不是一個常規文件,或者文件無法創建,或者由於其他原因無法打開文件, * 則會拋出FileNotFoundException異常 *@param append:true-表示向文件尾部追加寫入數據;false-表示覆蓋文件內容,默認爲false */ public FileOutputStream(File file, boolean append) throws FileNotFoundException { String name = (file != null ? file.getPath() : null); SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkWrite(name); } if (name == null) { throw new NullPointerException(); } if (file.isInvalid()) { throw new FileNotFoundException("Invalid file path"); } // BEGIN Android-changed: Open files through common bridge code. // this.fd = new FileDescriptor(); int flags = O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC); this.fd = IoBridge.open(name, flags); // END Android-changed: Open files through common bridge code. // Android-changed: Tracking mechanism for FileDescriptor sharing. // fd.attach(this); this.isFdOwner = true; this.append = append; this.path = name; // Android-removed: Open files through common bridge code. // open(name, append); // Android-added: File descriptor ownership tracking. IoUtils.setFdOwner(this.fd, this); // Android-added: CloseGuard support. guard.open("close"); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; File file = new File(filepath); if(!file.exists()){ file.createNewFile(); } FileOutputStream fos = new FileOutputStream(file,true); fos.write(byte b[])//調用write方法時候會向目標文件尾部追加內容,而不是覆蓋原來內容。
-
重要方法
write(int b)(內部調用write(byte b[] , int off, int len)方法實現)
/** *將指定的單個字節寫入此文件輸出流 */ public void write(int b) throws IOException { write(new byte[] { (byte) b }, 0, 1); } /** *示例代碼 */ String filePath = "/data/data/com.test/test.txt"; FileOutputStream fos = new FileOutputStream(filePath); byte byteData[] = "Hello".getBytes(); fos.write(byteData[0]); //寫入單個字節
write(byte b[])(內部調用write(byte b[] , int off, int len)方法實現)
/** *將指定的字節數組寫此入文件輸出流 */ public void write(byte b[]) throws IOException { write(b, 0, b.length); } /** *示例代碼 */ String filePath = "/data/data/com.test/test.txt"; FileOutputStream fos = new FileOutputStream(filePath); byte byteData[] = "Hello".getBytes(); fos.write(byteData); //寫入字節數組
write(byte b[], int off, int len)(以上Write方法都是基於該方法實現)
FileOutputStream的Write(byte b[],int off,int len)方法內部通過libcore庫JNI實現(
IoBridge.write(fd, b, off, len)
),我們無法查看具體實現,但是我們可以參考其父類OutputStream的Write(byte b[],int off,int len)的方法如下/** *將指定的字節數組b中的數據,從偏移量off開始寫出len長度到此文件輸出流 *@param b:要寫出到輸出流中的字節數組 *@param off:字節數組中偏移量 *@param len:需要寫出的長度 *PS:內部通過循環調用write()方法逐個字節寫出 */ public void write(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } for (int i = 0 ; i < len ; i++) { write(b[off + i]); } } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; FileOutputStream fos = new FileOutputStream(filePath); byte byteData[] = "Hello".getBytes(); fos.write(byteData,0,byteData.length); //寫入字節數組(偏移量爲0,寫入長度爲整個數組長度)
緩衝字節流
BufferedInpuStream/BufferedOutputStream是FileInputStream/FileOutputStream的子類,是字節流的包裝類,爲字節流提供了緩衝功能。
BufferedInputStream
字節輸入緩衝流,當我們通過read()讀取輸入流的數據時,BufferedInputStream會先將該輸入流的數據
分批的填入到緩衝區中
,然後從緩衝區中讀取數據
。每當緩衝區中的數據被讀完之後,輸入流會再次填充數據緩衝區;如此反覆,直到我們讀完輸入流數據位置。
-
構造方法
BufferedInputStream(InputStream in)
/** *指定一個字節輸入流,並且使用默認緩衝大小(8192字節)創建緩衝字節輸入流 */ public BufferedInputStream(InputStream in) { this(in, defaultBufferSize); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
BufferedInputStream(InputStream in, int size)
/** *指定一個字節輸入流和緩衝區大小,創建緩衝字節輸入流 */ public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; int bufSize = 8*1024; //指定緩衝區大小爲8kb BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath),bufSize);
-
重要方法
read()
/** *從緩衝區讀取單個字節 */ public synchronized int read() throws IOException { // 若已經讀完緩衝區中的數據,則調用fill()從輸入流讀取下一部分數據來填充緩衝區 if (pos >= count) { fill(); //填充之後,緩衝區依然沒有數據,已經讀取結束,返回-1 if (pos >= count) return -1; } // 否則從緩衝區中讀取指定的字節 return getBufIfOpen()[pos++] & 0xff; } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath)); int byteData = bis.read();
read(byte b[], int off, int len)
/** *一次性從緩衝區中讀取len長度的字節,填充到目標字節數組b的偏移off位置(內部使用了數組拷貝) *@param b:用於存放從緩衝區中讀取字節數據 *@param off:數組b偏移位置 *@param len:要從緩衝區讀取的字節長度 */ public synchronized int read(byte b[], int off, int len) throws IOException{ getBufIfOpen(); // Check for closed stream if ((off | len | (off + len) | (b.length - (off + len))) < 0) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } //讀取到指定長度的數據才返回 int n = 0; for (;;) { int nread = read1(b, off + n, len - n); if (nread <= 0) return (n == 0) ? nread : n; n += nread; if (n >= len) return n; // if not closed but no bytes available, return InputStream input = in; if (input != null && input.available() <= 0) return n; } } /** *將緩衝區中的數據寫入到字節數組b中。off是字節數組b的起始位置,len是寫入長度 */ private int read1(byte[] b, int off, int len) throws IOException { int avail = count - pos; /** *1.如果緩衝區內數據爲空,進一步判斷目標讀取長度與緩衝區大小 */ if (avail <= 0) { /** *1-1如果目標讀取的長度大於緩衝區的長度 並且沒有markpos,則直接從原始輸入流中 *進行讀取,從而避免無謂的COPY(從原始輸入流至緩衝區,讀取緩衝區全部數據,清空緩衝區, *重新填入原始輸入流數據)。 */ if (len >= getBufIfOpen().length && markpos < 0) { return getInIfOpen().read(b, off, len); } /** *1-2如果目標讀取長度小於緩衝區大小,則調用fill()從輸入流讀取下一部分數據來填充緩衝區 *再一次判斷緩衝區是數據是否有效,如果沒有則返回-1讀取完畢! */ fill(); avail = count - pos; if (avail <= 0) return -1; } /** *2.如果緩衝區內有數據,取目標讀取長度與緩衝區有效數據長度最小值,然後通過數組拷貝, * 將緩衝數組內數據拷貝到目標數組中的偏移off位置 */ int cnt = (avail < len) ? avail : len; System.arraycopy(getBufIfOpen(), pos, b, off, cnt); pos += cnt; return cnt; } // 獲取輸入流 private InputStream getInIfOpen() throws IOException { InputStream input = in; if (input == null) throw new IOException("Stream closed"); return input; } // 獲取緩衝 private byte[] getBufIfOpen() throws IOException { byte[] buffer = buf; if (buffer == null) throw new IOException("Stream closed"); return buffer; } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath)); byte buffer = new byte[8*1024]; int len = -1; while((len = bis.read(buffer,0,buffer.length)) != -1){ System.out.println(new String(buffer,0,buffer.length)); }
mark(int readlimit)
/** *標記“緩衝區”中當前位置 */ public synchronized void mark(int readlimit) { marklimit = readlimit; markpos = pos; }
reset()
/** *將“緩衝區”中當前位置重置到mark()所標記的位置 */ public synchronized void reset() throws IOException { getBufIfOpen(); // Cause exception if closed if (markpos < 0) throw new IOException("Resetting to invalid mark"); pos = markpos; }
注意:BufferedInputStream沒有重寫父類的read(byte b[])
方法,因此即使BufferedInputStream調用該方法,實際是父類執行,並不會觸發緩衝功能。
BufferedOutputStream
字節輸出緩衝流,當我們通過write()方法將字節數據寫出時候,BufferedOutputStream會先將字節數據寫入到內部的緩衝區,而不是目標文件,當緩衝區滿以後纔會將緩衝區數據一次寫出到輸出流。(注意:
如果寫出的字節數組大於緩衝區大小,則會直接寫出到輸出流,不在通過緩衝區
)
-
構造方法
BufferedOutputStream(OutputStream out)
指定一個輸出字節流作爲參數,創建一個默認緩衝數組大小爲8192字節的緩衝輸出流
BufferedOutputStream(OutputStream out) /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))
BufferedOutputStream(OutputStream out, int size)
接受一個輸出流和一個指定大小的緩衝數組作爲參數,創建一個緩衝輸出流
BufferedOutputStream(OutputStream out, int size); /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; int bufferSize = 8*1024; //指定緩衝區大小爲8kb BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath),bufferSize)
-
重要方法
write(int b)
將單個字節寫入緩衝區(
如果緩衝區已滿,則先刷新緩衝區,然後再寫入
)public void write(int b) throws IOException{ if (count >= buf.length) { flushBuffer(); } buf[count++] = (byte)b; } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath)); byte[] byteData = "Hello World!".getBytes(); bos.write(byteData[0]); //將單個字節寫入緩衝區
write(byte[] b, int off, int len)
將指定字節數組,從off位置開始,寫入len長度字節到緩衝區
1.如果寫入的字節長度len > 緩衝區大小,那麼先將緩衝區數據寫入到輸出流,然後再將此字節數組數據直接寫入到輸出流(不經過緩衝區)
;2.如果寫入的字節長度 > 緩衝區實際可用大小,則先將緩衝區數據寫入輸出流,然後再將當前字節數據寫入緩衝區中
;3.如果寫入的字節長度 < 當前緩衝區實際可用大小,則直接將當前要寫入的字節數據寫入緩衝區內
;public void write(byte[] b, int off, int len) throws IOException{ /** *如果請求長度超過了輸出緩衝區的大小,刷新輸出緩衝區,然後調用其構造函數中傳入的輸出流 *直接將數據寫入到底層輸出流 */ if (len >= buf.length) { flushBuffer(); out.write(b, off, len); return; } /** *如果請求長度大於緩衝區可用大小,先刷新緩衝區(將緩衝區數據寫入底層輸出流),然後再將 *請求數據copy到緩衝區 */ if (len > buf.length - count) { flushBuffer(); } System.arraycopy(b, off, buf, count, len); count += len; } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath)); byte[] byteData = "Hello World!".getBytes(); bos.write(byteData,0,byteData.length) //將字節數組指定長度字節寫入緩衝區
flush()
刷新緩衝輸出流。強制將"緩衝的數據"寫入到底層輸出流中
public void flush() throws IOException{ flushBuffer(); out.flush(); }
close()
關閉此輸出流並釋放與該流關聯的任何系統資源
public void close() throws IOException { try (OutputStream ostream = out) { flush(); } }
flushBuffer()
緩衝區刷新具體執行方法
private void flushBuffer() throws IOException { /** *如果緩衝區有效字節數大於0,則調用其構造函數中傳入的底層輸出流,將數據寫入底層輸出流, *然後將緩衝區有效字節數重置 */ if (count > 0) { out.write(buf, 0, count); count = 0; } }
注意:BufferedOutputStream並沒有重寫父類的write(byte b[])
方法,因此即使BufferedOutputStream使用該方法,本質是父類執行操作,不會出發緩衝功能。
字符流
簡介
字符流是字節流的包裝,字符流則可以直接接受字符串,它內部將字符串轉換成字節,底層設備永遠只接收字節數據。
注意:字符流自帶緩衝功能,如果字符流不調用close或flush方法,則不會輸出任何內容。因此在進行寫操作後要調用flush()
或者close()
來將緩衝區的數據寫到輸出流。可能會有人疑問了?既然字符流自帶緩衝功能,那還需要Buferedreader
和BufferedWriter
緩衝字符流乾什麼?它們有什麼區別呢?具體詳解見文尾《字符流自帶的緩衝區與BufferedReader/BuferedWriter中緩衝區的區別?》
基類字符流
Reader與Writer是Java IO中所有輸入輸出字符流的基類。Reader與InputStream類似,Writer與OutputStream類似,不同點在於,
Reader與Writer基於字符
而InputStream與OutputStream是基於字節
。換句話說,Reader與Writer用於讀取和寫入文本,而InputStream與OutputStream用於讀取和寫入原始字節。通常我們會使用Reader與Writer的子類,而不會直接使用Reader或Writer。具體子類可查看Java IO類概述表表格。
Reader
所有輸入字符流的父類
-
重要方法
read()
從字符輸入流中讀取單個字符並返回(內部調用
read(char cbuf[], int off, int len)
方法)public int read() throws IOException { char cb[] = new char[1]; if (read(cb, 0, 1) == -1) return -1; else return cb[0]; }
read(char cbuf[])
從字符輸入流中讀取指定長度的字符存儲在內存數組cbuf中(內部調用
read(char cbuf[], int off, int len)
方法)public int read(char cbuf[]) throws IOException { return read(cbuf, 0, cbuf.length); }
read(char cbuf[], int off, int len)
從字符輸入流中讀取len長度的字符,存放在內存數組cbuf的偏移off位置,該方法是抽象方法,由子類實現。
abstract public int read(char cbuf[], int off, int len) throws IOException;
Writer
所有輸出字符流的父類
-
重要方法
write(int c)
將單個字符寫入到輸出流(內部調用
write(char cbuf[])
方法)public void write(int c) throws IOException { synchronized (lock) { if (writeBuffer == null){ writeBuffer = new char[WRITE_BUFFER_SIZE]; } writeBuffer[0] = (char) c; write(writeBuffer, 0, 1); } }
write(char cbuf[])
將指定的字符數組寫入到輸出流(內部調用
write(char cbuf[])
方法)public void write(char cbuf[]) throws IOException { write(cbuf, 0, cbuf.length); }
write(char cbuf[], int off, int len)
將指定的字符數組中,偏移off開始,len長度的字符寫入輸出流(該方法是抽象方法,由子類實現)
abstract public void write(char cbuf[], int off, int len) throws IOException;
write(String str)
將指定的字符串寫入到字符輸出流(內部調用
write(String str, int off, int len)
方法)public void write(String str) throws IOException { write(str, 0, str.length()); }
write(String str, int off, int len)
從指定的字符串的off位置開始,將len長度的字符寫入到輸出流(內部將字符轉換爲字節數組調用了
write(char cbuf[], int off, int len)
方法)public void write(String str, int off, int len) throws IOException { synchronized (lock) { char cbuf[]; if (len <= WRITE_BUFFER_SIZE) { if (writeBuffer == null) { writeBuffer = new char[WRITE_BUFFER_SIZE]; } cbuf = writeBuffer; } else { // Don't permanently allocate very large buffers. cbuf = new char[len]; } str.getChars(off, (off + len), cbuf, 0); write(cbuf, 0, len); } }
flush()
刷新流,將緩衝區中保存的字符數據,強制寫出到目標(該方法是抽象方法,由子類實現)
abstract public void flush() throws IOException;
close()
關閉輸出流(該方法是抽象方法,由子類實現)
abstract public void close() throws IOException;
文件字符流
FileReader/FileWriter是InputStreamReader/OutputStreamWriter的子類,具備父類的基本特性。用於讀寫文件的字符流(
每次都是直接從文件單個字符讀寫,頻繁操作IO
),該類構造函數使用系統默認的字符集編碼。如果想要自己指定這些值,請使用FileInputStream/OutputStream來構造一個InputStreamReader/OuputStreamWrite。
FileReader
文件輸入字符流
-
構造方法
FileReader(String fileName)
指定一個文件名稱,來創建一個文件輸入字符流(內部調用父類(InputStreamReader)的構造方法實現,默認指定一個FileInputStream(fileName)文件輸入字節流和空的字符集)
public FileReader(String fileName) throws FileNotFoundException { super(new FileInputStream(fileName)); }
FileReader(File file)
指定一個實例文件,來創建一個文件輸入字符流(內部調用父類InputStreamReader(InputStream in)的構造方法實現,默認指定一個new FileInputStream(file)文件輸入字節流和空的字符集)
public FileReader(File file) throws FileNotFoundException { super(new FileInputStream(file)); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; File file = new File(filePath); if(!file.exists()){ file.createNewFile(); } FileReader fr = new FileReader(file); int charData = fr.read(); //從輸入流讀取單個字符 char buffer[] = new char[8*1024]; int len = fr.read(buffer) //從輸入流讀取指定長度字符,存儲在字符數組buffer中
FileWriter
文件輸出字符流
-
構造方法
FileWriter(String fileName)
指定一個文件名,創建一個文件輸出字符流(內部調用父類OutputStreamWriter(OutputStream out)的 構造方法實現,默認指定一個FileOutputStream(fileName)文件輸出字節流和空的字符集)
public FileWriter(String fileName) throws IOException { super(new FileOutputStream(fileName)); }
FileWriter(String fileName, boolean append)
指定一個文件名,和append表示符創建一個文件輸出字符流(內部調用父類 OutputStreamWriter(OutputStream out)的構造方法實現,默認指定一個 FileOutputStream(fileName, append)文件輸出字節流和空的字符集)
public FileWriter(String fileName, boolean append) throws IOException { super(new FileOutputStream(fileName, append)); }
FileWriter(File file)
指定一個實例文件,創建一個文件輸出字符流(內部調用父類 OutputStreamWriter(OutputStream out)的構造方法實現,默認指定一個FileOutputStream(file) 文件輸出字節流和空的字符集)
public FileWriter(File file) throws IOException { super(new FileOutputStream(file)); }
FileWriter(File file, boolean append)
指定一個實例文件,和append表示符創建一個文件輸出字符流(內部調用父類 OutputStreamWriter(OutputStream out)的構造方法實現,默認指定一個 FileOutputStream(file, append)文件輸出字節流和空的字符集)
public FileWriter(File file, boolean append) throws IOException { super(new FileOutputStream(file, append)); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; File file = new File(filePath); if(!file.exists()){ file.createNewFile(); } FileWriter fw = new FileWriter(file,false); String msg = "美國新冠病毒致死人數升至全球第一。特朗普政府想通過各種甩鍋行爲轉移國內矛盾!"; fw.write(msg.charAt(0)); //將單個字符寫入輸出流 fw.write(msg); //將字符串寫入輸出流 fw.write(char cbuf,int off,int len); //將指定字符數組,從off開始寫入len長度到輸出字符流 fw.write(String str,int off,int len); //將指定字符串,從off開始,寫入len長度字符到輸出字符流
緩衝字符流
BuffredReader/BufferedWriter 緩衝字符流,
它可以包裝字符流,爲字符流的讀寫提供緩衝功能
,減少直接與磁盤之間的I/O操作,提升讀寫效率。原理類似於BufferedInputStream/BufferedOutputStream緩衝字節流。BufferedReader/BufferedWriter內部默認大小爲8192字符長度的緩衝區。
- 當BufferedReader讀取文本文件時,會盡量讀取文本數據到緩衝區內,之後如果調用
read()
方法,會先從緩衝區讀取數據,如果緩衝區數據不夠,然後纔會從文件讀取;- 當使用BufferedWriter寫文本文件時,寫入的數據並不會立馬輸出到目的地,而是先存儲到緩衝區,如果緩衝區內數據存滿了,纔會一次性對目的地進行寫出;
BufferedReader
緩衝輸入字符流
-
構造方法
BufferedReader(Reader in)
通過包裝一個指定的字符輸入流,創建一個緩衝字符輸入流(使用默認的緩衝大小)
BufferedReader(Reader in)
BufferedReader(Reader in, int sz)
通過包裝一個指定的字符輸入流,並使用指定的緩衝大小創建一個緩衝字符輸入流
BufferedReader(Reader in, int sz) /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; String bufSize = 8*1024; BufferedReader br = new BufferedReader(new FileReader(filePath),bufSize); //從緩衝輸入流的緩衝區讀取單個字符 int charData = br.read(); //從輸入緩衝流的緩衝區讀取buffer.length長度的字符到內存字符數組buffer中 char buffer[] = new char[8*1024]; int len = br.read(buffer); //等同於 br.read(buffer,0,buffer.length);
-
重要方法
read()
從緩衝區讀取單個字符
- 如果緩衝區字符被讀取完,先調用fill()方法從字符輸入流中讀取一部分數據填充到緩衝區,然後再從緩衝區讀取一個字節(減少每次對磁盤的讀操作,降低對磁盤損耗)。
public int read() throws IOException { synchronized (lock) { ensureOpen(); for (;;) { if (nextChar >= nChars) { fill(); //先從輸入流讀取一部分數據填充數據到緩衝區 if (nextChar >= nChars) return -1; } if (skipLF) { skipLF = false; if (cb[nextChar] == '\n') { nextChar++; continue; } } return cb[nextChar++]; } } }
read(char[] cbuf, int off, int len)
從緩衝區讀取len長度的字符到內存數組cbuf的off位置
注意:
-
如果緩衝區沒有數據,分爲以下兩種情況:
讀取的len長度大於緩衝區大小,則從底層輸入流直接單個讀取len長度字符到內存數組cbuf中的off位置,不再經過緩衝區
;讀取的len長度小於緩衝區大小,先從底層輸入流讀取一部分數據填充到緩衝區,再從緩衝區讀取(copy))len長度字符到內存數組cbuf中的off位置
;
-
如果緩衝區有數據,分爲以下下兩種情況:
-
讀取的長度(len)大於緩衝區內有效的數據長度(n),則先從緩衝區內讀取餘下有效的長度(n)字符存放到數組cbuf內,再循環一次(緩衝區數據被讀完後再次讀取先fill填充)從緩衝區中讀取餘下長度(len-n)的字符到內存數據cbuf中一併返回
; -
讀取的長度(len)小於緩衝區內有效的數據長度,則直接從緩衝區內讀取(copy)len長度的字符到內存數組cbuf中的off位置
。
-
public int read(char cbuf[], int off, int len) throws IOException { synchronized (lock) { ensureOpen(); if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } //從緩衝區讀取字符的具體方法 int n = read1(cbuf, off, len); if (n <= 0) return n; //如果讀取的字符長度小於目標長度,則循環讀取,直到滿足目標讀取的長度len爲止 while ((n < len) && in.ready()) { int n1 = read1(cbuf, off + n, len - n); if (n1 <= 0) break; n += n1; } return n; } } /** *具體從緩衝區讀取字符數據的方法 */ private int read1(char[] cbuf, int off, int len) throws IOException { //如果緩衝區數據爲空 if (nextChar >= nChars) { //並且要讀取的長度大於緩衝區長度,則直接從底層流中讀取 if (len >= cb.length && markedChar <= UNMARKED && !skipLF) { return in.read(cbuf, off, len); } //否則先從底層流讀取數據填充到緩衝區 fill(); } if (nextChar >= nChars) return -1; if (skipLF) { skipLF = false; if (cb[nextChar] == '\n') { nextChar++; if (nextChar >= nChars) fill(); if (nextChar >= nChars) return -1; } } //計算要讀取的長度len與當前緩衝區內有效數據長度最小值 int n = Math.min(len, nChars - nextChar); //通過數組copy將緩衝區內的有效數據copy到內存數組cbuf的off爲止 System.arraycopy(cb, nextChar, cbuf, off, n); nextChar += n; return n; }
readLine()
從緩衝區內讀取一行,返回改行內的字符串String
public String readLine() throws IOException { return readLine(false); }
-
代碼示例
/** *創建一個輸入字符緩衝流 */ File file = new File(localPath, fileName); BufferedReader br = new BufferedReader(new FileReader(file)); //從緩衝區讀取單個字符 int charDara = br.read(); //從緩衝區讀取指定長度的字符,存放在內存數組cbuf的off位置 //注意:定義的數組大小盡可能不要超過緩衝區大小,否則read方法內部會直接從個底層流直接讀取而不是通過緩存) char cbuf[] = new char[8*1024]; int len = br.read(cbuf,0,cbuf.length); //從緩衝區中讀取一行字符串 String lineData = br.readLine();
注意:BufferedReader類沒有重寫父類的read(char cbuf[])
方法,因此即使使用BuferedReader來調用這個方法,本質還是由父類執行並不會出發緩衝區功能。
BufferedWriter
緩衝輸出字符流
-
構造函數
BufferedWriter(Writer out)
通過包裝一個指定的字符輸出流,創建一個緩衝字符輸出流(使用默認的緩衝大小)
public BufferedWriter(Writer out) { this(out, defaultCharBufferSize); }
BufferedWriter(Writer out, int sz)
通過包裝一個指定的字符輸出流,並指定緩衝區大小,創建一個緩衝字符輸出流
public BufferedWriter(Writer out, int sz) { super(out); if (sz <= 0) throw new IllegalArgumentException("Buffer size <= 0"); this.out = out; cb = new char[sz]; nChars = sz; nextChar = 0; lineSeparator = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("line.separator")); }
-
重要方法
write(int c)
將單個字符寫入到緩衝區。如果緩衝區已滿,則先刷新緩衝區,然後再寫入。
public void write(int c) throws IOException { synchronized (lock) { ensureOpen(); if (nextChar >= nChars) flushBuffer(); cb[nextChar++] = (char) c; } }
write(char cbuf[], int off, int len)
將指定的字符數組,從off位置開始,寫入len長度字符到緩衝區。
注意:
如果寫入的長度大於緩衝區大小,則先刷新緩衝區,然後將字符數組直接寫出到底層輸出流
。- 如果寫入的長度小於緩衝區大小,有以下兩種情況:
寫入的長度小於緩衝區當前可用大小,則直接將字符數組copy到緩衝區
;寫入的長度大於緩衝區當前可用大小,則先從數組中copy緩衝區剩餘大小的數據到緩衝區中,然後刷新緩衝區,將數據寫出到輸出流,然後再循環一次將剩下的數組數據copy到緩衝區中
;
public void write(char cbuf[], int off, int len) throws IOException { synchronized (lock) { ensureOpen(); if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } /** *如果寫入的字符長度大於緩衝區大小,則先刷新緩衝區,然後直接將字符數組寫入到輸出流 *而不是再寫入緩衝區。 */ if (len >= nChars) { flushBuffer(); out.write(cbuf, off, len); return; } 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; if (nextChar >= nChars) flushBuffer(); } } }
write(String s, int off, int len)
將指定字符串的數據,從off位置開始,寫入len長度到緩衝區
public void write(String s, int off, int len) throws IOException { synchronized (lock) { ensureOpen(); int b = off, t = off + len; while (b < t) { int d = min(nChars - nextChar, t - b); s.getChars(b, b + d, cb, nextChar); b += d; nextChar += d; if (nextChar >= nChars) flushBuffer(); } } }
-
代碼示例
/** *使用BufferedWriter包裝一個字符輸出流,調用write將指定的字符、字符數組、字符串寫入到緩衝區 */ String filePath = "/data/data/com.test/test.txt"; BufferedWriter bw = new BufferedWriter(new FileWriter(filePath)); String msg = "美國新冠病毒致死人數升至全球第一。特朗普政府想通過各種甩鍋行爲轉移國內矛盾!"; //將單個字符寫入緩衝區 bw.write(msg.charAt(0)); //將指定字符數組,從off開始寫入len長度字符到緩衝區 char charDatas[] = msg.toCharArray(); bw.write(charDatas, 0, charDatas.length); //將指定字符串,從off開始,寫入len長度字符到緩衝區 bw.write(msg,0,msg.lenght);
/** *結合BufferedReader來對文件進行讀寫 */ BufferedReader br = new BufferedReader(new FileReader(new File(fileName))); BufferedWriter bw = new BufferedWriter(new FileWriter(new File(writerFile))); //單個字符讀寫 int charData; while ((charData = br.read()) != -1) { bw.write(charData); } bw.close(); br.close(); //通過字符數組進行讀寫 char buffer[] = new char[8 * 1024]; int len; while ((len = br.read(buffer, 0, buffer.length)) != -1) { bw.write(buffer, 0, len); } bw.close(); br.close(); //通過字符串進行讀寫 String str; while ((str = br3.readLine()) != null) { bw3.write(str, 0, str.length()); } bw3.close(); br3.close();
注意:BufferedWrite類沒有重寫父類的write(char cbuf[])
和write(String str)
方法,因此即使使用BuferedWrite來調用這兩個方法,本質還是由父類執行並不會出發緩衝區功能。
轉換流
InputStreamReader
是從**
輸入字節流—>輸入字符流
**轉換流,它讀取字節並使用指定的字符集或者平臺默認的字符集將它們解碼爲字符。
-
構造方法
InputStreamReader(InputStream in)
通過指定輸入字節流,創建一個輸入轉換流(字節流->字符流)。
public InputStreamReader(InputStream in) { super(in); try { sd = StreamDecoder.forInputStreamReader(in, this, (String)null); } catch (UnsupportedEncodingException e) { // The default encoding should always be available throw new Error(e); } }
InputStreamReader(InputStream in, String charsetName)
通過指定輸入字節流和指定字符集,創建一個輸入轉換流(字節流—>字符流)
public InputStreamReader(InputStream in, String charsetName) throws UnsupportedEncodingException { super(in); if (charsetName == null) throw new NullPointerException("charsetName"); sd = StreamDecoder.forInputStreamReader(in, this, charsetName); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; String charset = "UTF-8"; //指定的字符集爲UTF-8編碼 InputStreamReader isr = new InputSteamReader(new FileInputStream(filePath),charset)
-
重要方法
read()
讀取單個字符
public int read() throws IOException { return sd.read(); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; InputStreamReader isr = new InputSteamReader(new FileInputStream(filePath)) int charData = isr.read();
read(char cbuf[], int offset, int length)
讀取length長度的字符,存儲到內存字符數組cbuf的偏移off位置。
public int read(char cbuf[], int offset, int length) throws IOException { return sd.read(cbuf, offset, length); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; InputStreamReader isr = new InputSteamReader(new FileInputStream(filePath)) char buffer[] = new char[8*1024]; int len = -1; while((len = isr.read(buffer,0,buffer.length)) != -1){ System.out.println(new String(buffer,0,buffer.length)); }
OutputStreamWriter
是從**
字符流—>字節流
**轉換流,它接受字符並使用指定的字符集或者平臺默認的字符集將他們編碼爲字節。
-
構造函數
OutputStreamWriter(OutputStream out)
指定字節輸出流,創建一個輸出轉換流(使用平臺默認字符集進行轉換),(字符->字節)
public OutputStreamWriter(OutputStream out) { super(out); try { se = StreamEncoder.forOutputStreamWriter(out, this, (String)null); } catch (UnsupportedEncodingException e) { throw new Error(e); } }
OutputStreamWriter(OutputStream out, String charsetName)
指定字節輸出流和字符編碼集,創建一個輸出轉換流(字符->字節)
public OutputStreamWriter(OutputStream out, String charsetName) throws UnsupportedEncodingException { super(out); if (charsetName == null) throw new NullPointerException("charsetName"); se = StreamEncoder.forOutputStreamWriter(out, this, charsetName); } /** *代碼示例 */ String filePath = "/data/data/com.test/test.txt"; String charset = "UTF-8"; OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath,charset));
-
重要方法
write(int c)
寫入單個字符
public void write(int c) throws IOException { se.write(c); }
write(char cbuf[], int off, int len)
將字符數組cbuf內數據,從off位置開始,指定len長度的字符寫入到字符輸出流
public void write(char cbuf[], int off, int len) throws IOException { se.write(cbuf, off, len); }
write(String str, int off, int len)
將指定字符串中的字符,從off位置開始,指定len長度字符寫入到字符輸出流
public void write(String str, int off, int len) throws IOException { se.write(str, off, len); } /** *代碼示例 */ String filePath = "/data/data/com.test.test.txt"; OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath)); String msg = "美國新冠病毒致死人數升至全球第一。特朗普政府想通過各種甩鍋行爲轉移國內矛盾!"; osw.write(msg,0,msg.length); osw.flush(); osw.close();
flush()
刷新流,將會把緩衝區中的字符,強制寫入到輸出流。
public void flush() throws IOException { se.flush(); }
close()
關閉輸出流
public void close() throws IOException { se.close(); }
擴展
IO的常規的read()讀操作爲什麼返回的是int類型?
-
確保返回類型一致
使用IO進行讀操作時候,如果文件讀取結束,那麼我們會接受到一個
-1
來表示文件已經讀取完畢,此時返回的-1
是int類型。倘若我們在正常讀時候(文件沒有讀取結束)返回byte類型,那麼顯然是無法通過一個方法返回兩種類型的,因此這裏統一返回爲int類型。 -
便於直觀觀察
在約定了返回爲int類型的情況下,我們每次read操作其實返回的是8位二進制數據,二進制數據不利於分析觀察,可以轉換成十進制來展示,因此需要把讀取的二進制字節轉換成十進制,因爲返回的是int類型
IO操作爲什麼需要緩衝?
原因很簡單,效率高
!緩衝中的數據實際上是保存在內存中,而原始數據可能是保存在硬盤或NandFlash等存儲介質中;而我們知道,從內存中讀取數據的速度比從硬盤讀取數據的速度至少快10倍以上。
- 那幹嘛不乾脆一次性將全部數據都讀取到緩衝中呢?
- 讀取全部的數據所需要的時間可能會很長。
- 內存價格很貴,容量不像硬盤那麼大。
字符流自帶的緩衝區與BufferedReader/BuferedWriter中緩衝區的區別?
Java 在IO操作中,都會有一個緩衝區的,它們不同在於緩衝區的大小。BufferWriter更合理的使用緩衝區,在你對大量的數據時,FileWrite的效率明顯不如BufferWriter。
- BuferedWriter
- 提供緩衝區(
默認8192字符=16384字節
),且可通過構造函數來修改(一般不需要)。 - 效率高:當緩衝區滿或者主動調用
flush()
或close()
方法,纔會通過OutputStreamWriter
中的StreamEncode
負責查詢碼錶(將字符轉換成字節數)。
- 提供緩衝區(
- FileWriter
- 嚴格來說自身不提供緩衝,而是其父類(``OutputStreamWriter
)通過
StreamEncoder將字符轉換爲字節存儲在
StreamEncoder`緩衝區中(DEFAULT_BYTE_BUFFER_SIZE = 8192) - 效率低:來一個字符查詢一次編碼表(通過
StreamEncoder
將字符轉換爲字節,存儲在緩衝字節數組中)
- 嚴格來說自身不提供緩衝,而是其父類(``OutputStreamWriter
執行流程圖
Android openFileInput與openFileOutput操作
Android中的openFileInput
與openFileOutput
是Android平臺提供基於Context上下文環境,對內部存空間即/data/data/packagename/files
路徑下文件進行讀寫操作的字節流。
代碼示例
/**
* 打開內部存儲當前應用files目錄下指定文件,寫入數據(如果文件不存在則創建)
*/
fun writeFile(context:Context){
val fileName = "test.txt"
val fos = this.openFileOutput(fileName, Context.MODE_PRIVATE)
fos.write("Hello World!".toByteArray())
fos.close()
}
/**
* 打開內部存儲當前應用files目錄下指定文件,讀取文件數據。
*(如果文件不存在,或者無法正常打開,則拋出異常:Caused by: java.io.FileNotFoundException)
*/
fun testOpenFileInput(){
val fileName2 = "test2.txt"
val fis = this.openFileInput(fileName2)
val buffer = ByteArray(8 * 1024)
val len = fis.read(buffer)
println(String(buffer, 0, len))
}
參考文章: