吃透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、視頻文件等等,字符流用來處理文本文件,可以看做是特殊的二進制文件,使用了某種編碼,人可以閱讀。

吃透Java基礎十二:IO

根據數據流向不同分類

  • 輸入流: 程序從輸入流讀取數據源。數據源包括外界(鍵盤、文件、網絡…),即是將數據源讀入到程序的通信通道
  • 輸出流:程序向輸出流寫入數據。將程序中的數據輸出到外界(顯示器、打印機、文件、網絡…)的通信通道。

二、源碼分析

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. 輸出結果如下圖所示。


吃透Java基礎十二:IO


運行輸出:


吃透Java基礎十二:IO


2、字節數組的輸入輸出

ByteArrayOutputStream :字節數組輸出流在內存中創建一個字節數組緩衝區,所有發送到輸出流的數據保存在該字節數組緩衝區中。

ByteArrayInputStream :字節數組輸入流在內存中創建一個字節數組緩衝區,從輸入流讀取的數據保存在該字節數組緩衝區中。


吃透Java基礎十二:IO


輸出:

[1, 2, 3]

3、對象的輸入輸出

java.io.ObjectOutputStream是實現序列化的關鍵類,它可以將一個對象轉換成二進制流通過write()方法寫入到OutputStream輸出流中。然後可以通過ObjectInputStream的read()方法將對應的InputStream二進制流還原成對象。


吃透Java基礎十二:IO


輸出

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作為控制檯的輸出與輸入。

單個字符輸入


吃透Java基礎十二:IO


整行字符輸入:


吃透Java基礎十二:IO


運行輸出:

請輸入一行字符:
bobo
輸入的內容為:bobo 

5、二進制文件的輸入輸出


吃透Java基礎十二:IO


輸出:


吃透Java基礎十二:IO


6、文本文件的輸入輸出

用FileWriter和FileReader讀取文本文件:


吃透Java基礎十二:IO


輸出:


吃透Java基礎十二:IO


使用字節流和字符流的轉換類 InputStreamReader 和 OutputStreamWriter 可以指定文件的編碼,使用 Buffer 相關的類來讀取文件的每一行。


吃透Java基礎十二:IO


運行輸出:

編碼方式為:GBK
波波測試文件的讀寫


分享到:


相關文章: