文章出自:聽雲博客
題主將以三個章節的篇幅來講解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