1. 程式人生 > >io流函式略解(java_input流)[二]

io流函式略解(java_input流)[二]

背景

在寫這篇隨筆之前,已經寫了io流函式略解(java_File)(一),主要是總結了File的一些操作,以及一些原始碼介紹。
在Io實際應用中,實際上運用在如果會操作File,實際上很難寫出一點能實際應用的code,因為操作檔案嘛,更多的是操作流,也就是steam。
下面將簡單總結一些流的概念,以及流的一些基本理論,同時也會貼出原始碼來略看。

實踐

io之所以叫io,i的意思是input,o的意思是output,也就是一個輸入一個輸出,分別對應read與write。

inputsteam

inputsteam 在java 中是一個abstract class。那麼它和介面是不一樣的,抽象類是可以有具體方法的甚至建構函式。

inputsteam是read操作,那麼看下在inputsteam有什麼read的函式吧。

/**
 * Reads the next byte of data from the input stream. The value byte is
 * returned as an <code>int</code> in the range <code>0</code> to
 * <code>255</code>. If no byte is available because the end of the stream
 * has been reached, the value <code>-1</code> is returned. This method
 * blocks until input data is available, the end of the stream is detected,
 * or an exception is thrown.
 *
 * <p> A subclass must provide an implementation of this method.
 *
 * @return     the next byte of data, or <code>-1</code> if the end of the
 *             stream is reached.
 * @exception  IOException  if an I/O error occurs.
 */
public abstract int read() throws IOException;

read 沒有實現,是一個抽象的方法。但是告訴了我們很有用的資訊。
如下:

  1. 返回的是一個位元組,返回是0-255。為什麼是0-255呢?因為一個位元組是8位,11111111不就是255嘛。

  2. 如果沒有了,則返回-1,為什麼會返回-1,因為-1最高效。解釋起來很複雜,可以關注我後面總結的資料結構。

  3. 如果錯誤會返回一個IOException 異常。

同樣,我找到了另外一個read

public int read(byte b[], int off, int len) throws IOException {
//判斷引數是否符合,比如說byte是否為空,然後off與len的一些基本要求,比如說一個正常的off肯定要>0,然後len>0,len還有大於b.length-off
//在看到 b.length - off的時候就可以確定off是針對b[]的,衝off開始,給b[]寫入或者替換資料。
    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;
    }
//為什麼要單獨寫一個呢?一個是優化,不需要構造for迴圈,第二個是可以提前檢查read錯誤
    int c = read();
    if (c == -1) {
        return -1;
    }
    b[off] = (byte)c;
//for 迴圈讀取,然後read -1則說明到底了。
    int i = 1;
    try {
        for (; i < len ; i++) {
            c = read();
            if (c == -1) {
                break;
            }
            b[off + i] = (byte)c;
        }
    } catch (IOException ee) {
    }
    return i;
}

這個我就不貼註釋了,有原始碼看啥註釋。一些關鍵點,我也給了自己的一些看法。

然後還有一個是:

public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
}

其實就是呼叫read(byte b[], int off, int len);
至此,read部分就基本在這了,當然有其他函數了,不可能一一來說明,用到的時候自然就ok的。

FileInputStream

我們在inputsteam有了一個大體的框架,然而呢,read沒有實現。那麼來看看到底是如何讀取檔案的吧,FileInputStream。
依然我們來看read:

public int read() throws IOException {
        return read0();
}
private native int read0() throws IOException;

出現了native,這表示是呼叫外部庫。native解釋起來有一丟丟麻煩,就是去呼叫不是java寫的庫了,例如呼叫c語言寫的函式庫,後面也寫一片總結吧。
好吧,read只能暫時介紹到這裡,操作一下吧。

 try(InputStream inputStream= new FileInputStream("xxxx"))
 {
     int n;
     while ((n=inputStream.read())!=-1) {
        System.out.println(n);

    }
 }catch (Exception e) {
    // TODO: handle exception
}

ps:

try(InputStream inputStream= new FileInputStream("xxxx"))這樣寫自動在finally中幫我們呼叫close方法,因為InputStream 繼承了java.lang.AutoCloseable 介面。
為什麼要close呢?因為要釋放資源啊,用完就放,輕裝前行。

ByteArrayInputStream

這個從字面意思是位元組陣列輸入流?意思就是把字元陣列轉換成InputStream。
例如:

public void ByteArrayInputStreamTest() throws IOException  {
  byte[] data={11,12,15,16};
  try(InputStream inputStream=new ByteArrayInputStream(data))
  {
      int n;
      while ((n = inputStream.read()) != -1) {
      }
  }
}

來看看原始碼實現吧:

  1. 看看它的超類
ByteArrayInputStream extends InputStream

這就解釋了為什麼可以這樣寫:

InputStream inputStream=new ByteArrayInputStream(data)
  1. 例項化:
public ByteArrayInputStream(byte buf[]) {
    this.buf = buf;
    this.pos = 0;
    this.count = buf.length;
}

在這裡我們可以想到read(byte b[], int off, int len),其實就是模擬把檔案中所有的位元組都讀出來了,然後給了裡面的一個buf 快取屬性。

Reader

InputStream 關於位元組流的,Reader 是關於字元流的。
我們知道位元組是byte,字元是char,兩者存在千絲萬縷的關係,他們中間的橋樑是編碼。編碼又是一個相當難以用一兩句話解釋的東西了,後續會新增一篇編碼的隨筆。
總之,看下Reader 到底幹什麼的吧。

// 讀取單個字元
public int read() throws IOException {
    char cb[] = new char[1];
    if (read(cb, 0, 1) == -1)
        return -1;
    else
        return cb[0];
}
// 抽象沒得實現
abstract public int read(char cbuf[], int off, int len) throws IOException;
//呼叫了抽象read(char cbuf[], int off, int len)
public int read(char cbuf[]) throws IOException {
    return read(cbuf, 0, cbuf.length);
}

好吧,沒有什麼具體的實現,那麼就去看看InputStreamReader吧,它的一個實現類。

InputStreamReader

根據上文,我們迫切需要知道的是abstract public int read(char cbuf[], int off, int len) throws IOException的實現方法。

public int read(char[] cbuf,
    int offset,
    int length) throws IOException 

{
        int off = offset;
        int len = length;
        synchronized (lock) {
            ensureOpen();
            if ((off <  0) || (off  > cbuf.length) || (len <  0) ||
                ((off + len)  > cbuf.length) || ((off + len) <  0)) {
                throw new IndexOutOfBoundsException();
            }
            if (len == 0)
                return 0;
            int n = 0;
            if (haveLeftoverChar) {
                // Copy the leftover char into the buffer
                cbuf[off] = leftoverChar;
                off++; len--;
                haveLeftoverChar = false;
                n = 1;
                if ((len == 0) || !implReady())
                    // Return now if this is all we can produce w/o blocking
                    return n;
            }
            if (len == 1) {
                // Treat single-character array reads just like read()
                int c = read0();
                if (c == -1)
                    return (n == 0) ? -1 : n;
                cbuf[off] = (char)c;
                return n + 1;
            }
            return n + implRead(cbuf, off, off + len);
        }
}

關鍵部分:

int c = read0();
if (c == -1){
  return (n == 0) ? -1 : n;
}
cbuf[off] = (char) c;

上文中提及到read0()是讀取一個位元組,然後把位元組轉換成字元。
ok,那麼我們就知道原理了。
實踐一下吧:

public void readFile() throws IOException {
    try (Reader reader = new FileReader("xxxx")) {
        char[] buffer = new char[1000];
        int n;
        while ((n = reader.read(buffer)) != -1) {
        }
    }
}

CharArrayReader與StringReader

簡單說明一下他們倆吧。

char[] test={'a','b'};
try (Reader reader = new CharArrayReader(test)) {
}
try (Reader reader = new StringReader("xxx")) {
}

就是把字元陣列或者字串專成了Reader。
以CharArrayReader為例:

  1. 繼承:
    public class CharArrayReader extends Reader
  2. 例項化
public CharArrayReader(char buf[]) {
    this.buf = buf;
    this.pos = 0;
    this.count = buf.length;
}
  1. 讀取
public int read() throws IOException {
    synchronized (lock) {
        ensureOpen();
        if (pos >= count)
            return -1;
        else
            return buf[pos++];
    }
}

就是模擬了假如全部的讀取檔案中的所有資料,然後轉換成了char[],快取起來。

總結

1.不管是一次性讀取byte[]還是一個一個讀byte,原理上都是一個一個讀的,只是byte[] 儲存起來了。
2.讀取字元流其實是在讀取位元組後轉換的。
3.避免忘記close,推薦使用try(){}這種語法。
4.對於像ByteArrayInputStream 這樣的轉換,其實是假設資料全部讀取出來了,然後進行操作