文章出自:聽雲博客
題主將以三個章節的篇幅來講解JAVA IO的內容 。
第一節JAVA IO包的框架體系和源碼分析,第二節,序列化反序列化和IO的設計模塊,第三節異步IO。
本文是第一節。
IO框架
從上圖我們可以看出IO可以分為兩大塊 字節流和字符流
字節流是 InputStream 和 OutputStream 分別對應輸入與輸出
字符流是Reader和Writer分別對應輸入與輸出
ByteArrayInputStream
它字節數組輸入流。繼承於InputStream。它包含一個數組實現的緩沖區ByteArrayInputStream也是數組實現的,提供read()來讀取數據,內部有一個計數器用來確定下一個要讀取的字節。
分析源碼
//pos是下一個會被讀取字節的索引
//count字節流的長度
//pos為0就是從0開始讀取
//讀取下一個字節, &0xff的意思是將高8位全部置0
// 將“字節流的數據寫入到字節數組b中”
// off是“字節數組b的偏移地址”,表示從數組b的off開始寫入數據
// len是“寫入的字節長度”
ByteArrayOutputStream
它是字節數組輸出流。繼承於OutputStream。ByteArrayOutputStream 實際也是數組實現的,它維護一個字節數組緩沖。緩沖區會自動擴容。
源碼分析
//我們看到不帶參的構造方法默認值是32 數組大小必須大於0否則會報 Negative initial size錯誤 ByteArrayOutputStream本質是一個byte數組
//是將字節數組buffer寫入到輸出流中,offset是從buffer中讀取數據的起始下標,len是寫入的長度。
//ensureCapacity方法是判斷數組是否需要擴容
//System.arraycopy是寫入的實現
//數組如果已經寫滿則grow
//int Capacity=oldCapacity<<1,簡單粗暴容量X2
Piped(管道)
多線程可以通過管道實現線程中的通訊,在使用管道時必須PipedInputStream,PipedOutputStream配套缺一不可
PipedInputStream
//初始化管道
//鏈接管道
//將“管道輸入流”和“管道輸出流”綁定。
//調用的是PipedOutputStream的connect方法
PipedOutputStream
//指定配對的PedpedInputStream
示例
package ioEx; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.IOException; public class PipedStreamEx { public static void main(String[] args) { Writer t1 = new Writer(); Reader t2 = new Reader(); //獲取輸入輸出流 PipedOutputStream out = t1.getOutputStream(); PipedInputStream in = t2.getInputStream(); try { //將管道連接 也可以這樣寫 out.connect(in); in.connect(out); t1.start(); t2.start(); } catch (IOException e) { e.printStackTrace(); } } } class Reader extends Thread { private PipedInputStream in = new PipedInputStream(); // 獲得“管道輸入流”對象 public PipedInputStream getInputStream(){ return in; } @Override public void run(){ readOne() ; //readWhile() ; } public void readOne(){ // 雖然buf的大小是2048個字節,但最多只會從輸入流中讀取1024個字節。 // 輸入流的buffer的默認大小是1024個字節。 byte[] buf = new byte[2048]; try { System.out.println(new String(buf,0,in.read(buf))); in.close(); } catch (IOException e) { e.printStackTrace(); } } // 從輸入流中讀取1024個字節 public void readWhile() { int total=0; while(true) { byte[] buf = new byte[1024]; try { int len = in.read(buf); total += len; System.out.println(new String(buf,0,len)); // 若讀取的字節總數>1024,則退出循環。 if (total > 1024) break; } catch (IOException e) { e.printStackTrace(); } } try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } class Writer extends Thread { private PipedOutputStream out = new PipedOutputStream(); public PipedOutputStream getOutputStream(){ return out; } @Override public void run(){ writeSmall1024(); //writeBigger1024(); } // 向輸出流中寫入1024字節以內的數據 private void writeSmall1024() { String strInfo = "I < 1024" ; try { out.write(strInfo.getBytes()); out.close(); } catch (IOException e) { e.printStackTrace(); } } // 向“管道輸出流”中寫入一則較長的消息 private void writeBigger1024() { StringBuilder sb = new StringBuilder(); for (int i=0; i<103; i++) sb.append("0123456789"); try { // sb的長度是1030 將1030個字節寫入到輸出流中, 測試一次只能讀取1024個字節 out.write( sb.toString().getBytes()); out.close(); } catch (IOException e) { e.printStackTrace(); } } }
ObjectInputStream 和 ObjectOutputStream
Object流可以將對象進行序列化操作。ObjectOutputStream可以持久化存儲對象, ObjectInputStream,可以讀出這些這些對象。
源碼很簡單直接上例子,關於序列化的內容題主將於下一節敘述
package ioEx; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Map; import java.util.Map.Entry; import java.util.HashMap; import java.util.Iterator; public class ObjectStreamTest { private static final String FILE_NAME= "test.txt"; public static void main(String[] args) { testWrite(); testRead(); } private static void testWrite() { try { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(FILE_NAME)); out.writeByte((byte)1); out.writeChar('a'); out.writeInt(20160329); out.writeFloat(3.14F); out.writeDouble(Math.PI); out.writeBoolean(true); // 寫入HashMap對象 Map<String,String> map = new HashMap<String,String>(); map.put("one", "one"); map.put("two", "two"); map.put("three", "three"); out.writeObject(map); // 寫入自定義的Box對象,Box實現了Serializable接口 Test test = new Test("a", 1, "a"); out.writeObject(test); out.close(); } catch (Exception ex) { ex.printStackTrace(); } } private static void testRead() { try { ObjectInputStream in = new ObjectInputStream(new FileInputStream(FILE_NAME)); System.out.println(in.readBoolean()); System.out.println(in.readByte()&0xff); System.out.println(in.readChar()); System.out.println(in.readInt()); System.out.println(in.readFloat()); System.out.println(in.readDouble()); Map<String,String> map = (HashMap) in.readObject(); Iterator<Entry<String, String>> iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<String, String> entry = (Map.Entry<String, String>)iter.next(); System.out.println(entry.getKey()+":"+ entry.getValue()); } Test test = (Test) in.readObject(); System.out.println("test: " + test); in.close(); } catch (Exception e) { e.printStackTrace(); } } } class Test implements Serializable { private String a; private int b; private String c; public Test(String a, int b, String c) { this.a = a; this.b = b; this.c = c; } @Override public String toString() { return "a, "+b+", c"; } }
FileInputStream 和 FileOutputStream
FileInputStream 是文件輸入流,它繼承於InputStream。我們使用FileInputStream從某個文件中獲得輸入字節。
FileOutputStream 是文件輸出流,它繼承於OutputStream。我們使用FileOutputStream 將數據寫入 File 或 FileDescriptor 的輸出流。
File操作十分簡單,這裡就不再展示示例了。
FilterInputStream
它的作用是用來封裝其它的輸入流,並為它們提供額外的功能。它的常用的子類有BufferedInputStream和DataInputStream和PrintStream。。
BufferedInputStream的作用就是為“輸入流提供緩沖功能,以及mark()和reset()功能”。
DataInputStream 是用來裝飾其它輸入流,它允許應用程序以與機器無關方式從底層輸入流中讀取基本 Java 數據類型。應用程序可以使用 DataOutputStream(數據輸出流)寫入由DataInputStream(數據輸入流)讀取的數據
(01) BufferedOutputStream的作用就是為“輸出流提供緩沖功能”。
(02) DataOutputStream 是用來裝飾其它輸出流,將DataOutputStream和DataInputStream輸入流配合使用,“允許應用程序以與機器無關方式從底層輸入流中讀寫基本 Java 數據類型”。
(03) PrintStream 是用來裝飾其它輸出流。它能為其他輸出流添加了功能,使它們能夠方便地打印各種數據值表示形式。
主要了解一下Buffered流
BufferedInputStream
它是緩沖輸入流。它繼承於FilterInputStream。它的作用是為另一個輸入流添加一些功能,例如,提供“緩沖功能”以及支持“mark()標記”和“reset()重置方法”。它本質上是通過一個內部緩沖區數組實現的
源碼分析
方法不再一一解讀,重點講兩個方法 read1 和 fill
根據fill()中的判斷條件可以分為五種情況
情況1:讀取完buffer中的數據,並且buffer沒有被標記
(01) if (markpos < 0) 它的作用是判斷“輸入流是否被標記”。若被標記,則markpos大於/等於0;否則markpos等於-1。
(02) 在這種情況下:通過getInIfOpen()獲取輸入流,然後接著從輸入流中讀取buffer.length個字節到buffer中。
(03) count = n + pos; 這是根據從輸入流中讀取的實際數據的多少,來更新buffer中數據的實際大小。
情況2:讀取完buffer中的數據,buffer的標記位置>0,並且buffer中沒有多余的空間
這種情況發生的情況是 — — 輸入流中有很長的數據,我們每次從中讀取一部分數據到buffer中進行操作。當我們讀取完buffer中的數據之後,並且此時輸入流存在標記時;那麼,就發生情況2。此時,我們要保留“被標記位置”到“buffer末尾”的數據,然後再從輸入流中讀取下一部分的數據到buffer中。
其中,判斷是否讀完buffer中的數據,是通過 if (pos >= count) 來判斷的;
判斷輸入流有沒有被標記,是通過 if (markpos < 0) 來判斷的。
判斷buffer中沒有多余的空間,是通過 if (pos >= buffer.length) 來判斷的。
理解這個思想之後,我們再對這種情況下的fill()代碼進行分析,就特別容易理解了。
(01) int sz = pos - markpos; 作用是“獲取‘被標記位置’到‘buffer末尾’”的數據長度。
(02) System.arraycopy(buffer, markpos, buffer, 0, sz); 作用是“將buffer中從markpos開始的數據”拷貝到buffer中(從位置0開始填充,填充長度是sz)。接著,將sz賦值給pos,即pos就是“被標記位置”到“buffer末尾”的數據長度。
(03) int n = getInIfOpen().read(buffer, pos, buffer.length - pos); 從輸入流中讀取出“buffer.length - pos”的數據,然後填充到buffer中。
(04) 通過第(02)和(03)步組合起來的buffer,就是包含了“原始buffer被標記位置到buffer末尾”的數據,也包含了“從輸入流中新讀取的數據”。
情況3:讀取完buffer中的數據,buffer被標記位置=0,buffer中沒有多余的空間,並且buffer.length>=marklimit
執行流程如下,
(01) read() 函數中調用 fill()
(02) fill() 中的 else if (pos >= buffer.length) ...
(03) fill() 中的 else if (buffer.length >= marklimit) ...
說明:這種情況的處理非常簡單。首先,就是“取消標記”,即 markpos = -1;然後,設置初始化位置為0,即pos=0;最後,再從輸入流中讀取下一部分數據到buffer中。
情況4:讀取完buffer中的數據,buffer被標記位置=0,buffer中沒有多余的空間,並且buffer.length<marklimit
執行流程如下,
(01) read() 函數中調用 fill()
(02) fill() 中的 else if (pos >= buffer.length) ...
(03) fill() 中的 else { int nsz = pos * 2; ... }
這種情況的處理非常簡單。
(01) 新建一個字節數組nbuf。nbuf的大小是“pos*2”和“marklimit”中較小的那個數。
(02) 接著,將buffer中的數據拷貝到新數組nbuf中。通過System.arraycopy(buffer, 0, nbuf, 0, pos)
(03) 最後,從輸入流讀取部分新數據到buffer中。通過getInIfOpen().read(buffer, pos, buffer.length - pos);
注意:在這裡,我們思考一個問題,“為什麼需要marklimit,它的存在到底有什麼意義?”我們結合“情況2”、“情況3”、“情況4”的情況來分析。
假設,marklimit是無限大的,而且我們設置了markpos。當我們從輸入流中每讀完一部分數據並讀取下一部分數據時,都需要保存markpos所標記的數據;這就意味著,我們需要不斷執行情況4中的操作,要將buffer的容量擴大……隨著讀取次數的增多,buffer會越來越大;這會導致我們占據的內存越來越大。所以,我們需要給出一個marklimit;當buffer>=marklimit時,就不再保存markpos的值了。
情況5:除了上面4種情況之外的情況
執行流程如下,
(01) read() 函數中調用 fill()
(02) fill() 中的 count = pos...
這種情況的處理非常簡單。直接從輸入流讀取部分新數據到buffer中。
BufferedOutputStream
BufferedOutputStream 是緩沖輸出流。它繼承於FilterOutputStream。
BufferedOutputStream 的作用是為另一個輸出流提供“緩沖功能”。
代碼很簡單,就不一一分析了 這裡只分析一下write方法
字符流和字符流的區別和使用
字符流的實現與字節流基本相同,最大的區別是字節流是通過byte[]實現的,字符流是通過char[]實現的,這裡就不在一一介紹了
按照以下分類我們就可以很清楚的了解在何時使用字節流或字符流
一、按數據源分類:
1 、是文件: FileInputStream, FileOutputStream, ( 字節流 )FileReader, FileWriter( 字符 )
2 、是 byte[] : ByteArrayInputStream, ByteArrayOutputStream( 字節流 )
3 、是 Char[]: CharArrayReader, CharArrayWriter( 字符流 )
4 、是 String: StringBufferInputStream, StringBufferOuputStream ( 字節流 )StringReader, StringWriter( 字符流 )
5 、網絡數據流: InputStream, OutputStream,( 字節流 ) Reader, Writer( 字符流 )
二、按是否格式化輸出分:要格式化輸出: PrintStream, PrintWriter
三、按是否要緩沖分:要緩沖: BufferedInputStream, BufferedOutputStream,( 字節流 ) BufferedReader, BufferedWriter( 字符流 )
四、按數據格式分:
1 、二進制格式(只要不能確定是純文本的) : InputStream, OutputStream 及其所有帶 Stream 結束的子類
2 、純文本格式(含純英文與漢字或其他編碼方式); Reader, Writer 及其所有帶 Reader, Writer 的子類
五、按輸入輸出分:
1 、輸入: Reader, InputStream 類型的子類
2 、輸出: Writer, OutputStream 類型的子類
六、特殊需要:
1 、從 Stream 到 Reader,Writer 的轉換類: InputStreamReader, OutputStreamWriter
2 、對象輸入輸出: ObjectInputStream, ObjectOutputStream
3 、進程間通信: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter
4 、合並輸入: SequenceInputStream
5 、更特殊的需要: PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader
原文鏈接:http://blog.tingyun.com/web/article/detail/351