文章出自:聽雲博客 題主將以三個章節的篇幅來講解JAVA IO的內容 。 第一節JAVA IO包的框架體系和源碼分析,第二節,序列化反序列化和IO的設計模塊,第三節非同步IO。 本文是第一節。 IO框架 從上圖我們可以看出IO可以分為兩大塊 位元組流和字元流 位元組流是 InputStream 和 Out ...
文章出自:聽雲博客
題主將以三個章節的篇幅來講解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