吃透Java基礎十二:IO

一、什麼是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

波波測試文件的讀寫