Android 善用Okio簡化處理I/O操作

來源:互聯網
上載者:User

標籤:

Okio庫是一個由square公司開發的,它補充了java.io和java.nio的不足,以便能夠更加方便,快速的訪問、儲存和處理你的資料。而OkHttp的底層也使用該庫作為支援。而在開發中,使用該庫可以大大給你帶來方便。

目前,Okio的最新版本是1.6.0,gradle的引用如下

compile ‘com.squareup.okio:okio:1.6.0‘

Okio中有兩個關鍵的介面,SinkSource,這兩個介面都繼承了Closeable介面;而Sink可以簡單的看做OutputStream,Source可以簡單的看做InputStream。而這兩個介面都是支援讀寫逾時設定的。結構圖如下

它們各自有一個支援緩衝區的子類介面,BufferedSink和BufferedSource,而BufferedSink有一個實作類別RealBufferedSink,BufferedSource有一個實作類別RealBufferedSource;此外,Sink和Source它門還各自有一個支援gzip壓縮的實作類別GzipSink和GzipSource;一個具有委託功能的抽象類別ForwardingSink和ForwardingSource;還有一個實作類別便是InflaterSource和DeflaterSink,這兩個類主要用於壓縮,為GzipSink和GzipSource服務;整體的結構圖如下

BufferedSink中定義了一系列寫入緩衝區的方法,比如write方法寫byte數組,writeUtf8寫字串,還有一些列的writeByte,writeString,writeShort,writeInt,writeLong,writeDecimalLong等等方法;

BufferedSource定義的方法和BufferedSink極為相似,只不過一個是寫一個是讀,基本上都是一一對應的,如readUtf8,readByte,readString,readShort,readInt等等等等。這兩個介面中的方法有興趣的點源碼進去看就可以了。

而這兩個支援緩衝區的介面的實作類別RealBufferedSink和RealBufferedSource都是通過封裝一個Sink+Buffer或者Source+Buffer來進行實現的。如所示

拿RealBufferedSink來舉例,實際調用的write的一系列方法,都是直接的對成員變數buffer進行的操作,當寫入buffer成功後,最後會調用一個方法將buffer中的內容寫入到sink中去,我們隨便拿一個方法看一下

    public BufferedSink writeLong(long v) throws IOException {        if(this.closed) {            throw new IllegalStateException("closed");        } else {            this.buffer.writeLong(v);            return this.emitCompleteSegments();        }    }

可以看到,首先會判斷closed成員變數是否是標記著關閉,如果已經關閉了則扔出一個異常,否則將內容寫入到buffer,寫入完成後調用了一個emitCompleteSegments的方法,該方法中做了什麼呢,沒錯,就是將buffer中的內容寫入到sink成員變數中去,然後將自身返回。

    public BufferedSink emitCompleteSegments() throws IOException {        if(this.closed) {            throw new IllegalStateException("closed");        } else {            long byteCount = this.buffer.completeSegmentByteCount();            if(byteCount > 0L) {                this.sink.write(this.buffer, byteCount);            }            return this;        }    }

這兩個實作類別的內部的所有方法都是類似的,這裡不一一展開。

而這一切的背後都是一個叫做Buffer的類在支援著緩衝區,Buffer是BufferedSink和BufferedSource的實作類別,因此它既可以用來讀資料,也可以用來寫資料,其內部使用了一個Segment和SegmentPool,維持著一個鏈表,其迴圈利用的機制和Android中Message的利用機制是一模一樣的。

final class SegmentPool {    static final long MAX_SIZE = 65536L;    static Segment next;    static long byteCount;    private SegmentPool() {    }    static Segment take() {        Class var0 = SegmentPool.class;        synchronized(SegmentPool.class) {            if(next != null) {                Segment result = next;                next = result.next;                result.next = null;                byteCount -= 2048L;                return result;            }        }        return new Segment();    }    static void recycle(Segment segment) {        if(segment.next == null && segment.prev == null) {            if(!segment.shared) {                Class var1 = SegmentPool.class;                synchronized(SegmentPool.class) {                    if(byteCount + 2048L <= 65536L) {                        byteCount += 2048L;                        segment.next = next;                        segment.pos = segment.limit = 0;                        next = segment;                    }                }            }        } else {            throw new IllegalArgumentException();        }    }}

內部一個成員變數next指向鏈表下一個元素,take方法首先判斷池中是否存在可用的,存在則返回,不存在則new一個,而recycle則是將不再使用的Segment重新扔到池中去。從而達到一個Segment池的作用。

而Okio暴露給外部使用的類便是Okio這個類,其內部有大量的靜態方法,包括通過一個Source獲得BufferedSource,通過一個Sink獲得一個BufferedSink。這個過程很簡單,我們調用Okio的buffer方法即可返回我們需要的,如下

Okio.buffer(sink)Okio.buffer(source)

但是上面兩個方法需要傳遞一個Sink或者Source,那麼這個Sink和Source又是如何獲得的呢。其實方法也在Okio這個類中。我們可以調用sink方法獲得一個Sink,調用source方法獲得一個Source,而資料的來源或者目的可以是一個File,一個輸入或者輸出資料流,一個Socket連結等等。如下

Okio.sink(new File("***"));Okio.sink(new FileOutputStream(new File("***")));Okio.sink(new Socket("***",8888));Okio.source(new File("***"));Okio.source(new FileInputStream(new File("***")));Okio.source(new Socket("****",8888));

這樣你可能還不過癮,那麼讓我們連起來應用一下,現在我們從本地讀一個檔案,讀完後再往另一個檔案中寫入內容。

public static void main(String[] args) {    Source source = null;    BufferedSource bufferedSource = null;    try {        File file = new File("resources/test.txt");        source = Okio.source(file);        bufferedSource = Okio.buffer(source);        String content = bufferedSource.readUtf8();        System.out.println(content);    } catch (FileNotFoundException e) {        e.printStackTrace();    } catch (IOException e) {        e.printStackTrace();    } finally {        closeQuietly(bufferedSource);    }    Sink sink = null;    BufferedSink bufferedSink = null;    try {        File dest = new File("resources/dest.txt");        sink = Okio.sink(dest);        bufferedSink = Okio.buffer(sink);        bufferedSink.writeUtf8("11111111111");    } catch (FileNotFoundException e) {        e.printStackTrace();    } catch (IOException e) {        e.printStackTrace();    } finally {        closeQuietly(bufferedSink);    }}public static void closeQuietly(Closeable closeable) {    if (closeable != null) {        try {            closeable.close();        } catch (RuntimeException rethrown) {            throw rethrown;        } catch (Exception ignored) {        }    }}

或許有時候網路請求中,我們需要使用到Gzip的功能,那麼,我們可以簡單的使用一下gzip的功能

public static void main(String[] args) {    Sink sink = null;    BufferedSink bufferedSink = null;    GzipSink gzipSink=null;    try {        File dest = new File("resources/gzip.txt");        sink = Okio.sink(dest);        gzipSink=new GzipSink(sink);        bufferedSink = Okio.buffer(gzipSink);        bufferedSink.writeUtf8("android vs ios");    } catch (FileNotFoundException e) {        e.printStackTrace();    } catch (IOException e) {        e.printStackTrace();    } finally {        closeQuietly(bufferedSink);    }    Source source = null;    BufferedSource bufferedSource = null;    GzipSource gzipSource=null;    try {        File file = new File("resources/gzip.txt");        source = Okio.source(file);        gzipSource=new GzipSource(source);        bufferedSource = Okio.buffer(gzipSource);        String content = bufferedSource.readUtf8();        System.out.println(content);    } catch (FileNotFoundException e) {        e.printStackTrace();    } catch (IOException e) {        e.printStackTrace();    } finally {        closeQuietly(bufferedSource);    }}public static void closeQuietly(Closeable closeable) {    if (closeable != null) {        try {            closeable.close();        } catch (RuntimeException rethrown) {            throw rethrown;        } catch (Exception ignored) {        }    }}

驗證是否正確的方法便是查看該寫入的檔案是否是亂碼,以及讀出來是否是原來的字串。

對比一下原來的gzip壓縮與解壓縮的方式,你就會發現還是簡單了不少的

public class GzipUtil {    /**     * GZIP壓縮     *     * @param data     * @return     */    public static byte[] gzip(byte[] data) throws Exception {        if (data == null || data.length == 0) {            return null;        }        ByteArrayOutputStream out = new ByteArrayOutputStream();        GZIPOutputStream zos;        BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data));        byte[] buf = new byte[512];        int len;        try {            zos = new GZIPOutputStream(out);            while ((len = bis.read(buf)) != -1) {                zos.write(buf, 0, len);                zos.flush();            }            bis.close();            zos.close();            return out.toByteArray();        } finally {            if (out != null) {                try {                    out.close();                } catch (Exception e2) {                }            }        }    }    /**     * Gzip解壓縮     * @param b     * @return     */    public static byte[] unGzip(byte[] b) {        if (b == null || b.length == 0) {            return null;        }        ByteArrayOutputStream out = new ByteArrayOutputStream();        ByteArrayInputStream in = new ByteArrayInputStream(b);        try {            GZIPInputStream gunzip = new GZIPInputStream(in);            byte[] buffer = new byte[256];            int n;            while ((n = gunzip.read(buffer)) >= 0) {                out.write(buffer, 0, n);            }            return out.toByteArray();        } catch (IOException e) {            Log.e(WDCore.getInstance().getConfiguration().getLogTag(), "uncompress error", e);        } finally {            try {                if (out != null) {                    out.close();                }                if (in != null) {                    in.close();                }            } catch (Exception e2) {            }        }        return null;    }}

此外還有一個ByteString類,這個類可以用來做各種變化,它將byte轉會為String,而這個String可以是utf8的值,也可以是base64後的值,也可以是md5的值,也可以是sha256的值,總之就是各種變化,最後取得你想要的值。

總之,在合適的地方適當使用一下Okio這個庫,一定能給你開發帶來諸多便利,何樂而不為呢!

Android 善用Okio簡化處理I/O操作

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.