一、什麼是IO流
Java中將輸入輸出抽象稱為流,就好像水管,將兩個容器連接起來。流是一組有順序的,有起點和終點的字節集合,是對數據傳輸的總稱或抽象。即數據在兩設備間的傳輸稱為流。
按數據來源(去向)分類:
- 文件:FileInputStream、FileOutputStream、FileReader、FileWriter
- 數組:字節數組(byte[]):ByteArrayInputStream、ByteArrayOutputStream
- 字符數組(char[]):CharArrayReader、CharArrayWriter
- 管道操作:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter
- 基本數據類型:DataInputStream、DataOutputStream
- 緩衝操作:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
- 對象序列化反序列化:ObjectInputStream、ObjectOutputStream
- 打印:PrintStream、PrintWriter
- 轉換:InputStreamReader、OutputStreWriter
根據處理數據類型不同分類
- 字節流:數據流中最小的數據單元是字節。
- 字符流:數據流中最小的數據單元是字符, Java中的字符是Unicode編碼,一個字符佔用兩個字節。
字節流讀取單個字節,字符流讀取單個字符,字節流用來處理二進制文件如:圖片、MP3、視頻文件等等,字符流用來處理文本文件,可以看做是特殊的二進制文件,使用了某種編碼,人可以閱讀。
根據數據流向不同分類
- 輸入流: 程序從輸入流讀取數據源。數據源包括外界(鍵盤、文件、網絡…),即是將數據源讀入到程序的通信通道
- 輸出流:程序向輸出流寫入數據。將程序中的數據輸出到外界(顯示器、打印機、文件、網絡…)的通信通道。
二、源碼分析
IO 類雖然很多,但最基本的是 4 個抽象類:InputStream、OutputStream、Reader、Writer。最基本的方法也就是一個讀 read() 方法、一個寫 write() 方法。方法具體的實現還是要看繼承這 4 個抽象類的子類,畢竟我們平時使用的也是子類對象。
字節輸入流:InputStream
public abstract class InputStream implements Closeable {
//讀取一個字節數據,並返回讀到的數據,如果返回-1,表示讀到了輸入流的末尾。
public abstract int read() throws IOException;
//將數據讀入一個字節數組,同時返回實際讀取的字節數。如果返回-1,表示讀到了輸入流的末尾。
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
//將數據讀入一個字節數組,同時返回實際讀取的字節數。如果返回-1,表示讀到了輸入流的末尾。
//off指定在數組b中存放數據的起始偏移位置;len指定讀取的最大字節數。
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++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
//在輸入流中跳過n個字節,並返回實際跳過的字節數。
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
//返回在不發生阻塞的情況下,可讀取的字節數。
public int available() throws IOException {
return 0;
}
//關閉輸入流,釋放和這個流相關的系統資源。
public void close() throws IOException {}
//標記讀取位置,下次還可以從這裡開始讀取,使用前要看當前流是否支持,
//可以使用 markSupport() 方法判斷
public synchronized void mark(int readlimit) {}
//返回到上一個標記。
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
//測試當前流是否支持mark和reset方法。如果支持,返回true,否則返回false。
public boolean markSupported() {
return false;
}
}
字節輸出流:OutputStream
public abstract class OutputStream implements Closeable, Flushable {
//往輸出流中寫入一個字節。
public abstract void write(int b) throws IOException;
//往輸出流中寫入數組b中的所有字節。
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
//往輸出流中寫入數組b中從偏移量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;
}
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
//強制刷新,將緩衝中的數據寫入
public void flush() throws IOException {
}
//關閉輸出流,釋放和這個流相關的系統資源。
public void close() throws IOException {
}
}
```
**字符輸入流Reader**
```java
public abstract class Reader implements Readable, Closeable {
//讀取字符到字符緩存中
public int read(java.nio.CharBuffer target) throws IOException {
int len = target.remaining();
char[] cbuf = new char[len];
int n = read(cbuf, 0, len);
if (n > 0)
target.put(cbuf, 0, n);
return n;
}
//讀取一個字符,返回值為讀取的字符
public int read() throws IOException {
char cb[] = new char[1];
if (read(cb, 0, 1) == -1)
return -1;
else
return cb[0];
}
//讀取一系列字符到數組cbuf[]中,返回值為實際讀取的字符的數量
public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}
//讀取len個字符,從數組cbuf[]的下標off處開始存放,返回值為實際讀取的字符數量,該方法必須由子類實現
abstract public int read(char cbuf[], int off, int len) throws IOException;
//跳過指定長度的字符數量
public long skip(long n) throws IOException {
if (n < 0L)
throw new IllegalArgumentException("skip value is negative");
int nn = (int) Math.min(n, maxSkipBufferSize);
synchronized (lock) {
if ((skipBuffer == null) || (skipBuffer.length < nn))
skipBuffer = new char[nn];
long r = n;
while (r > 0) {
int nc = read(skipBuffer, 0, (int)Math.min(r, nn));
if (nc == -1)
break;
r -= nc;
}
return n - r;
}
}
//告訴此流是否已準備好被讀取。
public boolean ready() throws IOException {
return false;
}
//判斷當前流是否支持標記流
public boolean markSupported() {
return false;
}
//標記讀取位置,下次還可以從這裡開始讀取,使用前要看當前流是否支持,
//可以使用 markSupport() 方法判斷
public void mark(int readAheadLimit) throws IOException {
throw new IOException("mark() not supported");
}
//返回到上一個標記。
public void reset() throws IOException {
throw new IOException("reset() not supported");
}
//關閉輸入流,釋放和這個流相關的系統資源。
abstract public void close() throws IOException;
}
字符輸出流Writer
public abstract class Writer implements Appendable, Closeable, Flushable {
//將整型值c的低16位寫入輸出流
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);
}
}
//將字符數組cbuf[]寫入輸出流
public void write(char cbuf[]) throws IOException {
write(cbuf, 0, cbuf.length);
}
//將字符數組cbuf[]中的從索引為off的位置處開始的len個字符寫入輸出流
abstract public void write(char cbuf[], int off, int len) throws IOException;
//將字符串str中的字符寫入輸出流
public void write(String str) throws IOException {
write(str, 0, str.length());
}
//將字符串str 中從索引off開始處的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);
}
}
//追加寫入一個字符序列
public Writer append(CharSequence csq) throws IOException {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
}
//追加寫入一個字符序列的一部分,從 start 位置開始,end 位置結束
public Writer append(CharSequence csq, int start, int end) throws IOException {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
}
//追加寫入一個 16 位的字符
public Writer append(char c) throws IOException {
write(c);
return this;
}
//強制刷新,將緩衝中的數據寫入
abstract public void flush() throws IOException;
//關閉輸出流,流被關閉後就不能再輸出數據了
abstract public void close() throws IOException;
}
三、運用場景
1、字節流與字符流的轉換
- InputStreamReader:將一個字節流中的字節解碼成字符。
- OutputStreamWriter:將寫入的字符編碼成字節後寫入一個字節流。
字節流和字符流轉換看一下例子就行了:
1. 創建一個文本文件:bobo.txt,然後寫入內容:波波。
2. 用FileInputStream讀出文本內容然後以字節數組的形式緩存在fileInputStream裡面。
3. 用InputStreamReader 把fileInputStream緩存的字節數組讀取到定義的charArray的字符數組裡面,並且輸出。
4. 用OutputStreamWriter把字符數組charArray裡面的字符寫到InputStreamReader的字節數組緩存起來,然後輸出。
5. 輸出結果如下圖所示。
運行輸出:
2、字節數組的輸入輸出
ByteArrayOutputStream :字節數組輸出流在內存中創建一個字節數組緩衝區,所有發送到輸出流的數據保存在該字節數組緩衝區中。
ByteArrayInputStream :字節數組輸入流在內存中創建一個字節數組緩衝區,從輸入流讀取的數據保存在該字節數組緩衝區中。
輸出:
[1, 2, 3]
3、對象的輸入輸出
java.io.ObjectOutputStream是實現序列化的關鍵類,它可以將一個對象轉換成二進制流通過write()方法寫入到OutputStream輸出流中。然後可以通過ObjectInputStream的read()方法將對應的InputStream二進制流還原成對象。
輸出
false
list:[1, 2, 3] list1:[1, 2, 3]
4、控制檯的輸入輸出
- System.out:標準的輸出流,out是System類裡面的靜態變量,其類型是PrintStream,PrintStream繼承於FilterOutputStream,也是輸出類OutputStream的子類。
- System.in:標準的輸入流,in是System類裡面的靜態變量,其類型是
InputStream。
我們常用的System.out和System.in作為控制檯的輸出與輸入。
單個字符輸入
整行字符輸入:
運行輸出:
請輸入一行字符:
bobo
輸入的內容為:bobo
5、二進制文件的輸入輸出
輸出:
6、文本文件的輸入輸出
用FileWriter和FileReader讀取文本文件:
輸出:
使用字節流和字符流的轉換類 InputStreamReader 和 OutputStreamWriter 可以指定文件的編碼,使用 Buffer 相關的類來讀取文件的每一行。
運行輸出:
編碼方式為:GBK
波波測試文件的讀寫