Java&Android 基礎知識梳理(6) 位元組輸入輸出流

澤毛發表於2017-12-21

一、概述

Java IO庫中的代表有能力產出資料的資料來源物件或者是有能力接收資料的接收端物件,我們一般把它分成輸入和輸出兩部分:

  • 繼承自InputStreamReader派生的類都含有名為read的方法,用於讀取單個位元組或位元組陣列。
  • 繼承自OuputStreamWriter派生的類都含有名為write的方法,用於寫入單個位元組或位元組陣列。

我們通常通過疊合多個物件來提供所期望的功能,這其實是一種裝飾器設計模式。

二、位元組輸入流

2.1 InputStream的作用

它的作用是用來表示那些從不同資料來源產生輸入的類,而最終結果就是通過read方法獲得資料來源的內容,從資料來源中讀出的內容intbyte[]來表示

  • 位元組陣列
  • String物件
  • 檔案
  • 管道
  • 一個由其它種類的流組成的序列,以便我們可以將它們收集合併到一個流內
  • 其它資料來源,如網路等

2.2 InputStream原始碼

InputStream是一個抽象類,所有表示位元組輸入的流都是繼承於它,它實現了以下介面:

Java&Android 基礎知識梳理(6)   位元組輸入輸出流
比較關鍵的是前面四個方法:

  • public abstract int read() throws IOException 返回輸入流的下一個位元組(next byte,如果已經到達輸入流的末尾,那麼返回-1
  • public int read(byte b[]) throws IOException 嘗試從輸入流中讀取b.length長度的位元組,存入到b中,如果已經到達末尾返回-1,否則返回成功寫入到b中的位元組數。
  • public int read(byte b[], int off, int len) throws IOException 嘗試從輸入流中讀取下len長度的位元組,如果len0,那麼返回0,否則返回實際讀入的位元組數,讀入的第一個位元組存放在資料b[off]中,如果沒有可讀的,那麼返回-1
  • public long skip(long n) throws IOException 跳過,並丟棄掉n個位元組,其最大值為2048

2.3 InputStream的具體實現類

  • ByteArrayInputStream 它接收byte[]作為建構函式的引數,我們呼叫read方法時,就是從byte[]陣列裡,讀取位元組。
    public ByteArrayInputStream(byte buf[], int offset, int length) {
        this.buf = buf;
        this.pos = offset;
        this.count = Math.min(offset + length, buf.length);
        this.mark = offset;
    }

    public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }
複製程式碼
  • StringBufferInputStream 已經過時,推薦使用StringReader
  • FileInputStream FileInputStream支援提供檔名、FileFileDescription作為建構函式的引數,它的read呼叫的是底層的native方法。
    public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        fd = new FileDescriptor();
        fd.attach(this);
        path = name;
        open(name);
    }

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

    private native int read0() throws IOException;
複製程式碼
  • PipedInputStream 通過通訊管道來交換資料,如果兩個執行緒希望進行資料的傳輸,那麼它們一個建立管道輸出流,另一個建立管道輸入流,它必須要和一個PipedOutputStream相連線。
    public PipedInputStream(int pipeSize) {
        if (pipeSize <= 0) {
            throw new IllegalArgumentException("pipe size " + pipeSize + " too small");
        }
        buffer = new byte[pipeSize];
    }

    public PipedInputStream(PipedOutputStream out, int pipeSize) throws IOException {
        this(pipeSize);
        connect(out);
    }

    @Override
    public synchronized int read() throws IOException {
        if (!isConnected) {
            throw new IOException("Not connected");
        }
        if (buffer == null) {
            throw new IOException("InputStream is closed");
        }
        lastReader = Thread.currentThread();
        try {
            int attempts = 3;
            while (in == -1) {
                // Are we at end of stream?
                if (isClosed) {
                    return -1;
                }
                if ((attempts-- <= 0) && lastWriter != null && !lastWriter.isAlive()) {
                    throw new IOException("Pipe broken");
                }
                notifyAll();
                wait(1000);
            }
        } catch (InterruptedException e) {
            IoUtils.throwInterruptedIoException();
        }
        int result = buffer[out++] & 0xff;
        if (out == buffer.length) {
            out = 0;
        }
        if (out == in) {
            in = -1;
            out = 0;
        }
        notifyAll();
        return result;
    }
複製程式碼
  • SequenceInputStream 將多個InputStream連線在一起,一個讀完後就完畢,並讀下一個,它接收兩個InputStream物件或者一個容納InputStream物件的容器Enumeration
    public int read() throws IOException {
        while (in != null) {
            int c = in.read();
            if (c != -1) {
                return c;
            }
            nextStream();
        }
        return -1;
    }
複製程式碼
  • ObjectInputStream 它和一個InputStream相關聯,源資料都是來自於這個InputStream,它繼承於InputStream,並且它和傳入的InputStream並不是直接關聯的,中間通過了BlockDataInputStream進行中轉,要關注的就是它的readObject方法,它會把一個之前序列化過的物件進行反序列化,然後得到一個Object物件,它的目的在於將(把二進位制流轉換成為物件)和(從某個資料來源中讀出位元組流)這兩個操作獨立開來,讓它們可以隨意地組合
    public ObjectInputStream(InputStream in) throws IOException {
        verifySubclass();
        bin = new BlockDataInputStream(in);
        handles = new HandleTable(10);
        vlist = new ValidationList();
        serialFilter = ObjectInputFilter.Config.getSerialFilter();
        enableOverride = false;
        readStreamHeader();
        bin.setBlockDataMode(true);
    }

    public final Object readObject() throws IOException, ClassNotFoundException {
        if (enableOverride) {
            return readObjectOverride();
        }

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }
複製程式碼
  • FilterInputStream 它的建構函式引數就是一個InputStream
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        return in.read();
    }
複製程式碼

這個類很特殊,前面的InputStream子類都是傳入一個資料來源(Pipe/byte[]/File)等等,然後通過重寫read方法從資料來源中讀取資料,而FilterInputStream則是將InputStream組合在內部,它呼叫in去執行InputStream定義的抽象方法,也就是說它不會改變組合在內部的InputStream所對應的資料來源。 另外,它還新增了一些方法,這些方法底層還是呼叫了read方法,但是它封裝了一些別的操作,比如DataInputStream中的readInt,它呼叫in連續讀取了四次,然後拼成一個int型返回給呼叫者,之所以採用組合,而不是繼承,目的是將(把二進位制流轉換成別的格式)和(從某個資料來源中讀出位元組流)這兩個操作獨立開來,讓它們可以隨意地組合

    public final int readInt() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        int ch3 = in.read();
        int ch4 = in.read();
        if ((ch1 | ch2 | ch3 | ch4) < 0)
            throw new EOFException();
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
    }
複製程式碼

三、位元組輸出流

3.1 OuputStream的作用

OuputStream決定了輸出要去往的目標:

  • 位元組陣列
  • 檔案
  • 管道

3.2 OutputStream原始碼

InputStream類似,也是一個抽象類,它的子類代表了輸出所要去往的目標,它的關鍵方法如下:

Java&Android 基礎知識梳理(6)   位元組輸入輸出流
我們主要關注的是write方法,前兩個write方法最終都是呼叫了抽象的write(int oneByte)方法,最終怎麼寫入是由子類實現的。

3.3 OutputStream的具體實現類

  • ByteArrayOutputStreamByteArrayOutputStream的內部,有一個可變長的byte[]陣列,當我們呼叫write方法時,就是向這個陣列中寫入資料,它還提供了toByteArray/toString方法,來獲得當前內部byte[]陣列中的內容。
  • FileOutputStream 它和上面類似,只不過寫入的終點換成了所開啟的檔案。
  • PipedOutputStreamPipedInputStream相關聯。
  • ObjectOutputStreamObjectInputStream類似,只不過它內部組合的是一個OutputStream,當呼叫writeObject(Object object)方法時,其實是先將Object進行反序列化轉換為byte,再輸出到OuputStream所指向的目的地。
  • FilterOutputStream 它的思想和FilterInputStream類似,都是在內部組合了一個OuputStreamFilterOutputStream提供了寫入int/long/shortwrite過載函式,當我們呼叫這些函式之後,FilterOutputStream最終會通過內部的OuputStream向它所指向的目的地寫入位元組。

相關文章