精华内容
下载资源
问答
  • okio

    2020-03-15 23:12:58
    大概是最完全的Okio源码解析文章 用轻和快定义优雅,Okio框架解析
    展开全文
  • okio1.13.0

    2018-09-25 11:09:32
    OkHttp这个框架是一个比较好用的网络请求框架,okhttp使用所必需的库okio.jar
  • okio-1.13.0

    2019-12-03 16:18:55
    由于使用 okhttp ,okhttp 内使用 okio ,所以需要 okio.jar ,okio.jar 由于1.16版本以上使用了 Kotlin ,所以建议暂时使用okio.jar
  • okio-1.14.1

    2018-05-10 16:58:28
    OkHttp这个框架是一个比较好用的网络请求框架,okhttp使用所必需的库okio.jar,这个是okio-1.14.1,2018年okio的最新版本!
  • okio-1.9.0

    2016-08-06 10:30:34
    okio-1.9.0
  • okhttp-okio

    2018-12-13 10:34:02
    内包含okhttp-3.01.0 和okio-2.1.0 Java android 开发网络请求依赖包
  • okio-1.10.0

    2016-09-16 20:37:21
    okio-1.10.0.jar
  • okhttp和okio

    2018-12-03 13:10:22
    okhttp-3.8.0和okio-1.9.0,可以用于https传输。其中引入okhttp-3.8.0的格式为import okhttp3.*;
  • android Okio

    2020-04-16 11:44:56
    深入理解okio的优化思想 Android 善用Okio简化处理I/O操作
    展开全文
  • okttp包含okio

    2017-11-07 11:47:07
    压缩包内包含okhttp-2.5.0.jar和okio-1.6.0.jar,在移动应用开发必备
  • Android Okio使用

    2021-05-12 16:16:09
    Okio使用 概述 Okio是对Java IO的封装,存储和处理数据变得更加容易。 依赖库 implementation 'com.squareup.okio:okio:2.4.3' 基本使用 写操作 try (BufferedSink sink = Okio.buffer(Okio.sink(new File("text....

    Okio使用

    概述

    Okio不是用来完全替代Java IO,Okio本身是基于Java IO,存储和处理数据变得更加容易,Okio的执行效率比Java IO快了很多。

    Okio同时也是OkHttp的底层IO库。

    依赖库

    implementation 'com.squareup.okio:okio:2.4.3'
    

    基本使用

    写操作

    try (BufferedSink sink = Okio.buffer(Okio.sink(new File("text.txt")))) {
        sink.writeInt(65);
        sink.writeUtf8("hello okio");
        sink.writeUtf8("安卓");
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    读操作

    try (BufferedSource source = Okio.buffer(Okio.source(new File("text.txt")))) {
        String s = source.readUtf8();
        System.out.println(s);
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    文件复制

    try (BufferedSource source = Okio.buffer(Okio.source(new File("text.txt")));
         BufferedSink sink = Okio.buffer(Okio.sink(new File("new_text.txt")))) {
        byte[] bytes = new byte[1024];
        int len;
        while ((len = source.read(bytes)) != -1) {
            sink.write(bytes, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    等价于

    try (BufferedSource bufferedSource = Okio.buffer(Okio.source(new File("text.txt")));
         BufferedSink bufferedSink = Okio.buffer(Okio.sink(new File("new_text.txt")))) {
        bufferedSink.writeAll(bufferedSource);
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    Gzip

    写入数据

    try (Sink sink = Okio.sink(new File("text.txt"));
         GzipSink gzipSink = new GzipSink(sink);
         BufferedSink bufferedSink = Okio.buffer(gzipSink)) {
    
        bufferedSink.writeUtf8("hello");
        bufferedSink.writeUtf8("安卓");
    
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    

    读取数据

    try {
        Source source = Okio.source(new File("text.txt"));
        GzipSource gzipSource = new GzipSource(source);
        BufferedSource bufferedSource = Okio.buffer(gzipSource);
        String s = bufferedSource.readUtf8();
        System.out.println(s);
    
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    展开全文
  • okio-1.4.0

    2015-08-27 15:24:08
    okio-1.4.0 okhttp相关联的jar文件
  • okio和okhttpjar包

    2018-12-19 09:28:25
    okio和okhttpjar包 OkHttpClient所需要的jar包。。。。。
  • okio-1.16.0.jar

    2018-12-13 14:53:30
    由于使用 okhttp ,okhttp 内使用 okio ,所以需要 okio.jar ,okio.jar 由于1.16版本以上使用了 Kotlin ,所以建议暂时使用okio.jar 版本不大于 1.16. 所以自己生成的,本来不要分,发现最多是一分
  • Okio源码解析

    2020-02-14 17:40:55
    Okio源码解析 Okio是对java原生io的封装,旨在简化api同时优化io操作性能。接下来我会从下面几个方面介绍 Okio特性概述 读写流程源码查看 Buffer精华操作 Timeout超时处理 1. Okio特性概述 java已经提供了很多io...

    Okio是对java原生io的封装,旨在简化api同时优化io操作性能。接下来我会从下面几个方面介绍

    1. Okio特性概述
    2. 读写流程源码查看
    3. Buffer精华操作
    4. Timeout超时处理

    1. Okio特性概述

    java已经提供了很多io实现类供我们在不同场景使用,而Okio并不是一种新的io,而是对原生io的一次封装,为的是解决原生io的一些缺陷,下面我们介绍Okio的特性

    1.1 api简化

    我们知道java的io相关的类非常多,有针对字节和字符的输入输出接口,有实现缓存的Bufferedxxx,以及各种子类实现比如文件的(FileInputStream和FileOutputStream),数据的(DataInputStream和DataOutputStream),对象的(ObjectInputStream和ObjectOutputStream)等等,针对不同的场景我们需要使用不同的类,是非常复杂的。

    而Okio简化了这一流程,统一通过Okio这个工厂类初始化,内部是通过重载方法根据传入的参数不同初始化不同的流。

    举个板栗

            File file = new File(Environment.getExternalStorageState(), "test");
            Okio.buffer(Okio.sink(file)).writeString("aaaa", Util.UTF_8).close();
    

    由于传入的参数是File,内部是通过FileOutputStream进行io写操作,并且支持链式操作。

    1.2 缓存优化

    原生的io缓存比较粗暴,Okio在这方面做了很多优化。

    以BufferedOutputStream为例,是利用一个长度为8192的byte数组缓存,当要写入数据长度大于缓存最大容量时跳过缓存直接进行io写操作,当写入数据长度大于缓存数组剩余容量的时候先把缓存写入输出流,再将数据写入缓存,其他情况直接写入缓存。

    然后原生的输入输出流之间的buffer是没有办法直接建立联系的,输入流中的数据转移到输出流中是:输入buf -> 临时byte数组 -> 输出buf,经历两次拷贝

            //原生
            File file = new File(Environment.getExternalStorageState(), "test");
            try {
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
                int count = -1;
                byte[] array = new byte[1024];//临时byte数组
                while ((count = bis.read(array)) != -1) {
                    bos.write(array, 0, count);
                }
                bis.close();
                bos.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    

    okio的话则是用一个Buffer对象去负责缓存,内部维护了一个Segment双向链表,而这个Segment则是真正的数据缓存载体,内部利用一个长度为8192的byte数组去缓存,当要写入的数据超过8192的时候则会创建多个Segment对象在链表中有序存储。当Segment使用完毕的时候又会利用SegmentPool进行回收,减少Segment对象创建。

    对于输入流流到输出流有专门的优化,直接将输入流buffer中segment数据转移到输出流的buffer中只有一次数据的操作,相比原生粗暴的buffer做了很多优化。(而实际操作是,如果是整段的segment数据转移则是直接修改指针指到输出流的buffer上,如果只转移输入流Segment部分数据则根据输出Segment能不能装得下,装得下的话则进行数据拷贝,否则拆分输入流Segment为两个然后将要转移数据的Segment指针指向输出流Buffer,总之Okio在这块做了很多优化,这个后面会细说)

    1.3 超时操作

    原生进行io操作的时候是阻塞式的,没有超时的处理,除非发生异常才会停止,okio增加了超时处理,推出了Timeout机制,提供了同步超时和异步超时处理。

    同步超时Timeout:是在每次进行读写前检测是否超时,如果超时则抛出异常,那如果检测完之后操作阻塞了很久是没法停止的,只有等到下一次操作的时候才会抛出异常停止操作。

    异步超时AsyncTimeout:是在每次要进行读写操作的时候创建一个AsymcTimeout对象,然后通过一个链表存储,根据即将超时时间排序,快要超时的排在链表头部,然后启动一个Watchdog线程进行检测,优先从链表头部取出AsyncTimeout判断是否超时,超时了的话则调用AsyncTimeout#timeout()方法。okio给用socket创建流提供了默认实现,timeout的时候直接关闭Socket。

    1.4 相关类介绍

    • Source:Okio对输入流的抽象接口,提供了read方法将数据读到buffer中
    • Sink:Okio对输出流的抽象接口,提供了write方法将数据写到buffer中
    • BufferedSource和BufferedSink:是Okio对Buffer的接口抽象,分别继承Source和Sink
    • RealBufferedSource:BufferedSource实现类,读操作都是由该类来完成,可以通过Okio工厂获取
    • RealBufferedSink:BufferedSink实现类,写操作都是由该类来完成,可以通过Okio工厂获取
    • Okio:okio的工厂来用来获取buffer实现类和流的实现类
    • Segment:Buffer中存储数据的载体,内部通过一个8K的byte数组存储数据
    • SegmentPool:管理Segment创建和回收,最多存储64K的数据也就是8个Segment,当池中存在Segment的时候会复用减少对象的创建

    以读写文件为例,读是先文件内容读到buffer中,然后再从buffer中获取数据,写是先写数据到buffer中,然后将将buffer中的数据写入文件,而buffe中数据是通过一个个Segment存储的,Segment则是通过SegmentPool创建和回收,每个类各司其职。

    2. 读写流程源码

    2.1 读流程

    		File file = new File(Environment.getExternalStorageState(), "test");
        try {
             BufferedSource source = Okio.buffer(Okio.source(file));
             byte[] array = new byte[1024];
             source.read(array);
             source.close();
         } catch (Exception e) {
             e.printStackTrace();
         }
    

    根据上面的例子我们分几段查看源码

    1. Okio.source()
    2. Okio.buffer()
    3. BufferedSource.read()

    2.1.1 Okio.source()

      //Okio#source
    	public static Source source(File file) throws FileNotFoundException {
        if (file == null) throw new IllegalArgumentException("file == null");
        return source(new FileInputStream(file));
      }
    

    可以看到由于我们传入的是file所以内部实际是通过FileInputStream读文件

      public static Source source(InputStream in) {
        return source(in, new Timeout());
      }
    
      private static Source source(final InputStream in, final Timeout timeout) {
        if (in == null) throw new IllegalArgumentException("in == null");
        if (timeout == null) throw new IllegalArgumentException("timeout == null");
    
        return new Source() {
          @Override public long read(Buffer sink, long byteCount) throws IOException {
            if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
            if (byteCount == 0) return 0;
            try {
              timeout.throwIfReached();//判断是否超时
              Segment tail = sink.writableSegment(1);//从buffer中拿到一个可写的Segment
              int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);//byteCount是要读取的数据总量,Segment.SIZE - tail.limit是Segment可以装的数据量,取最小值
              int bytesRead = in.read(tail.data, tail.limit, maxToCopy);//然后通过FileInputStream将文件数据读到segment的data中
              if (bytesRead == -1) return -1;
              tail.limit += bytesRead;
              sink.size += bytesRead;
              return bytesRead;
            } catch (AssertionError e) {
              if (isAndroidGetsocknameError(e)) throw new IOException(e);
              throw e;
            }
          }
    
          @Override public void close() throws IOException {
            in.close();
          }
    
          @Override public Timeout timeout() {
            return timeout;
          }
    
          @Override public String toString() {
            return "source(" + in + ")";
          }
        };
      }
    
    

    上面这段就是创建了一个Source实现类,read的话则是先获取buffer中可写的segment,然后用FileInputStream将文件的数据读到segment中。对于Segment和Buffer在下面会说到

    2.1.2 Okio.Buffer()

      //Okio#buffer
    	public static BufferedSource buffer(Source source) {
        return new RealBufferedSource(source);
      }
    

    由于我们传入的是Source所以会创建一个RealBufferedSource实例,Okio也都是通过它来做读操作,我们先来看下这个类

    final class RealBufferedSource implements BufferedSource {
      public final Buffer buffer = new Buffer();//buffer对象
      public final Source source;//Source实现类
    
      RealBufferedSource(Source source) {
        if (source == null) throw new NullPointerException("source == null");
        this.source = source;
      }
      
      @Override 
      public int readInt() throws IOException {
        require(4);//最终通过source#read()将数据读到Buffer
        return buffer.readInt();//再从Buffer读取数据
      }
      
      @Override 
      public int read(byte[] sink, int offset, int byteCount) throws IOException {
        checkOffsetAndCount(sink.length, offset, byteCount);
    
        if (buffer.size == 0) {
          long read = source.read(buffer, Segment.SIZE);//先将数据读到Buffer
          if (read == -1) return -1;
        }
    
        int toRead = (int) Math.min(byteCount, buffer.size);
        return buffer.read(sink, offset, toRead);//再从Buffer读取数据
      }
      
      @Override 
      public String readString(Charset charset) throws IOException {
        if (charset == null) throw new IllegalArgumentException("charset == null");
    
        buffer.writeAll(source);//最终通过source#read()将数据读到Buffer
        return buffer.readString(charset);//再从Buffer读取数据
      }
      
      //后面省略了很多个read方法
    }
    

    可以看到它先将Okio.buffer(Okio.source(file))传入的Source实现类通过source成员变量存储,然后创建了一个Buffer对象用作缓冲区,而read方法都是一个流程通过source#read()方法将数据写入缓冲区,然后再从缓冲区中读取数据。接下来我们在看下缓冲区Buffer对象

    public final class Buffer implements BufferedSource, BufferedSink, Cloneable, ByteChannel {
      @Nullable Segment head;//缓存数据的载体
      long size;//当前缓冲区的数据量单位字节
      public Buffer() {
      }
      //下面省略很多读写缓冲区的操作
    }
    

    Buffer是负责管理缓冲区的对象,内部则是通过一个双向环状链表Segment存储数据,然后我们再来看下Segment是如何存储数据的呢

    final class Segment {
      static final int SIZE = 8192;//最大存储8K数据
      final byte[] data;//存储数据数组
      int pos;//当前segment数据已经读取到哪了,接下来从pos位置开始读
      int limit;//当前segment数据写到哪了,接下来从limit开始写
      boolean owner;//当前segment是否允许追加数据,也就是能不能多次写入
      Segment next;
      Segment prev;
    
      Segment() {
        this.data = new byte[SIZE];
        this.owner = true;//默认可以多次写入
        this.shared = false;
      }
    
    

    可以看到segment内部有一个8k的byte数组来存储数据,pos记录的是segment可读数据位置,(pos-1)到0是已经读过的数据,limit是segment可写数据的位置,limit到Segment.SIZE是剩余可写数据量,pos到limit是还未读取的数据。

    ok那我们回到本例中的source.read(array)

      @Override public int read(byte[] sink) throws IOException {
        return read(sink, 0, sink.length);
      }
    
      @Override public int read(byte[] sink, int offset, int byteCount) throws IOException {
        checkOffsetAndCount(sink.length, offset, byteCount);//数组相关检查
    
        if (buffer.size == 0) {//如果缓冲区是空的
          long read = source.read(buffer, Segment.SIZE);//将source中的数据读取到buffer中
          if (read == -1) return -1;
        }
    
        int toRead = (int) Math.min(byteCount, buffer.size);
        return buffer.read(sink, offset, toRead);//将数据写入sink也就是byte数组
      }
    

    对于source#read我们在2.1.1已经看过就是取出Buffer中的Segment,然后将数据写入。不过细节当时我们没说现在来看下

    //source#read()
    @Override public long read(Buffer sink, long byteCount) throws IOException {
            try {
              Segment tail = sink.writableSegment(1);//从buffer中拿到一个可写的Segment
              int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);//byteCount是要读取的数据总量,Segment.SIZE - tail.limit是Segment可写数据量,取最小值
              int bytesRead = in.read(tail.data, tail.limit, maxToCopy);//然后通过FileInputStream将文件数据读到segment的data数组中
              if (bytesRead == -1) return -1;
              tail.limit += bytesRead;//修改segment可写位置
              sink.size += bytesRead;//修改buffer中数据总量
              return bytesRead;
            } catch (AssertionError e) {
              if (isAndroidGetsocknameError(e)) throw new IOException(e);
              throw e;
            }
          }
    

    buffer#writableSegment(1)如何获取Segment的

      Segment writableSegment(int minimumCapacity) {
        if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();
    
        if (head == null) {//如果buffer中没有Segment
          head = SegmentPool.take(); //从SegmentPool中获取
          return head.next = head.prev = head;//维护Buffer内部的Segment双向环状链表
        }
    
        Segment tail = head.prev;//拿到最后一个Segment
        if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {//如果最后一个Segment可写位置limit+需要的最小容量>Segment.SIZE || 该Segment不支持追加写入
          tail = tail.push(SegmentPool.take()); // 添加一个新的Segment到尾部
        }
        return tail;
      }
    

    如果Buffer中没有Segment则直接从SegmentPool获取,如果有则获取链表尾部也就是最新的Segment,判断数据是否存的下,存不下的话从SegmentPool获取一个新的插到链表尾部

    接下来看下SegmentPool#take如何创建Segment的

    final class SegmentPool {
      static final long MAX_SIZE = 64 * 1024; // 64 KiB.最大容量64K
      static @Nullable Segment next;//通过一个单链表存储回收的Segment
      static long byteCount;//当前SegmentPool容量
    
      private SegmentPool() {
      }
    
      static Segment take() {
        synchronized (SegmentPool.class) {
          if (next != null) {//如果当前有回收的Segment
            Segment result = next;
            next = result.next;
            result.next = null;
            byteCount -= Segment.SIZE;
            return result;
          }
        }
        return new Segment(); //否则直接创建
      }
    
      static void recycle(Segment segment) {
        if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
        if (segment.shared) return; //当前Segment如果有共享数据不能回收(这个后面说)
        synchronized (SegmentPool.class) {
          if (byteCount + Segment.SIZE > MAX_SIZE) return; //当前SegmentPool满了的话则不能回收
          byteCount += Segment.SIZE;//容量增加
          segment.next = next;//加到链表中
          segment.pos = segment.limit = 0;//可写和可读位置都清零
          next = segment;
        }
      }
    }
    
    

    SegmentPool#take就是看当前的池子中有缓存的Segment的么,有直接使用,没有则创建一个。ok在回到最前面RealBufferedSource#read

    @Override 
      public int read(byte[] sink, int offset, int byteCount) throws IOException {
        checkOffsetAndCount(sink.length, offset, byteCount);
    
        if (buffer.size == 0) {
          long read = source.read(buffer, Segment.SIZE);//先将数据读到Buffer
          if (read == -1) return -1;
        }
    
        int toRead = (int) Math.min(byteCount, buffer.size);
        return buffer.read(sink, offset, toRead);//再从Buffer读取数据
      }
    

    第一块source#read(buffer, Segment.SIZE)已经梳理了一遍就是通过FileInputStream将数据读到Buffer的Segment中,然后再来buffer#read将数据读到byte数组中

      @Override public int read(byte[] sink, int offset, int byteCount) {
        checkOffsetAndCount(sink.length, offset, byteCount);
    
        Segment s = head;//拿到第一个Segment
        if (s == null) return -1;
        int toCopy = Math.min(byteCount, s.limit - s.pos);//判断要读取的数据量,取byteCount和当前segment可读的数据量s.limit - s.pos
        System.arraycopy(s.data, s.pos, sink, offset, toCopy);//将数据拷贝到数组中
    
        s.pos += toCopy;//移动Segment已经读过的数据指针pos
        size -= toCopy;//当前Buffer容量减去读过数据量
    
        if (s.pos == s.limit) {//如果当前Segment已经读完
          head = s.pop();//从链表中脱离
          SegmentPool.recycle(s);//SegmentPool尝试回收
        }
    
        return toCopy;
      }
    

    将Buffer中Segment数据拷贝到数组中,如果Segment数据已经读完则从Buffer链表中脱离,SegmentPool尝试回收。

    ok那读流程就讲完了,我们回顾下大体流程

    BufferedSource source = Okio.buffer(Okio.source(file));
             byte[] array = new byte[1024];
             source.read(array);
             source.close();
    
    • Okio.Source():是创建了一个Source实现类,提供read的能力,需要传入一个Buffer来获取数据,数据读取是通过FileInputStream来读取的,写入到Buffer的Segment中。
    • Okio.buffer():则创建了一个Buffer实现类RealBufferedSource,内部维护了一个环形双向链表Segment,是我们真正用来读取数据的对象。
    • BufferedSource.read():读取数据操作,流程是先将数据读到buffer中,然后再从buffer中读取数据。

    2.2 写流程

    写流程是读流程反过来,先将数据写到buffer,然后在从buffer写到文件中,大体跟前面差不多我们快速说一下

                byte[] array = new byte[1024];
                BufferedSink sink = Okio.buffer(Okio.sink(file));
                sink.write(array);
                sink.close();
    

    2.2.1 Okio.sink()

    先看Okio#sink(file)

      public static Sink sink(File file) throws FileNotFoundException {
        if (file == null) throw new IllegalArgumentException("file == null");
        return sink(new FileOutputStream(file));//创建FileOutputStream
      }
    
      public static Sink sink(OutputStream out) {
        return sink(out, new Timeout());
      }
    
      private static Sink sink(final OutputStream out, final Timeout timeout) {
        if (out == null) throw new IllegalArgumentException("out == null");
        if (timeout == null) throw new IllegalArgumentException("timeout == null");
    
        return new Sink() {
          @Override public void write(Buffer source, long byteCount) throws IOException {
            checkOffsetAndCount(source.size, 0, byteCount);
            while (byteCount > 0) {//遍历将Buffer中数据都写入到文件中
              timeout.throwIfReached();
              Segment head = source.head;//获取buffer中的segment
              int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
              out.write(head.data, head.pos, toCopy);//写入到文件
    
              head.pos += toCopy;
              byteCount -= toCopy;
              source.size -= toCopy;
    
              if (head.pos == head.limit) {
                source.head = head.pop();//segment脱链
                SegmentPool.recycle(head);//回收
              }
            }
          }
    
          @Override public void flush() throws IOException {
            out.flush();
          }
    
          @Override public void close() throws IOException {
            out.close();
          }
    
          @Override public Timeout timeout() {
            return timeout;
          }
    
          @Override public String toString() {
            return "sink(" + out + ")";
          }
        };
      }
    

    内部通过FileOutputStream进行io操作,通过一个while循环将Buffer中Segment数据统统写入到文件中。

    2.2.2 Okio.buffer()

      public static BufferedSink buffer(Sink sink) {
        return new RealBufferedSink(sink);
      }
    

    创建一个RealBufferedSink

    final class RealBufferedSink implements BufferedSink {
      public final Buffer buffer = new Buffer();//创建缓冲区
      public final Sink sink;//真正的io操作对象
      boolean closed;
      
        RealBufferedSink(Sink sink) {
        if (sink == null) throw new NullPointerException("sink == null");
        this.sink = sink;
      }
      
       @Override public BufferedSink write(byte[] source) throws IOException {
        if (closed) throw new IllegalStateException("closed");
        buffer.write(source);//写流程第一步是将数据写到buffer
        return emitCompleteSegments();//然后将Buffer数据写到文件中
      }
      
        @Override public BufferedSink emitCompleteSegments() throws IOException {
        if (closed) throw new IllegalStateException("closed");
        long byteCount = buffer.completeSegmentByteCount();
        if (byteCount > 0) sink.write(buffer, byteCount);//然后将Buffer数据写到文件中
        return this;
      }
    }
    

    内部创建了一个Buffer作为缓存区,并将Sink通过成员变量存储起来,写数据则是先将数据写到buffer中,在由Buffer通过FileOutputStream写到文件中。

    接下来看看 buffer#write(source)是如何将数据写入Buffer的

      @Override public Buffer write(byte[] source) {
        if (source == null) throw new IllegalArgumentException("source == null");
        return write(source, 0, source.length);
      }
    
      @Override public Buffer write(byte[] source, int offset, int byteCount) {
        if (source == null) throw new IllegalArgumentException("source == null");
        checkOffsetAndCount(source.length, offset, byteCount);
    
        int limit = offset + byteCount;//byte数组需要写到的最后一位下标
        while (offset < limit) {
          Segment tail = writableSegment(1);//获取可写的Segment
    
          int toCopy = Math.min(limit - offset, Segment.SIZE - tail.limit);
          System.arraycopy(source, offset, tail.data, tail.limit, toCopy);//数据拷贝到Segment中
    
          offset += toCopy;
          tail.limit += toCopy;
        }
    
        size += byteCount;
        return this;
      }
    

    循环的获取一个可写的Segment将数据写到当中,直到byte数组中写完。然后就是RealBufferedSink#emitCompleteSegments将Buffer数据写到文件中

    @Override public BufferedSink emitCompleteSegments() throws IOException {
        if (closed) throw new IllegalStateException("closed");
        long byteCount = buffer.completeSegmentByteCount();//获取写完的Segment中byte数
        if (byteCount > 0) sink.write(buffer, byteCount);//然后将Buffer数据写到文件中
        return this;
      }
    

    buffer#completeSegmentByteCount()

      public long completeSegmentByteCount() {
        long result = size;
        if (result == 0) return 0;
    
        // Omit the tail if it's still writable.
        Segment tail = head.prev;
        if (tail.limit < Segment.SIZE && tail.owner) {//最后一个Segment如果没装满则暂不写入文件
          result -= tail.limit - tail.pos;
        }
    
        return result;
      }
    

    获取写满的Segment字节数

    3. Buffer精华操作

    除了前面看到的对缓冲区的优化接下来看看Okio对于输入流流到输出流的优化

            try {
                BufferedSource source = Okio.buffer(Okio.source(file));
                BufferedSink sink = Okio.buffer(Okio.sink(file));
                sink.writeAll(source);
                sink.close();
                source.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    

    先看sink#writeAll()

      @Override public long writeAll(Source source) throws IOException {
        if (source == null) throw new IllegalArgumentException("source == null");
        long totalBytesRead = 0;
        for (long readCount; (readCount = source.read(buffer, Segment.SIZE)) != -1; ) {//for循环将数据读到输出流的buffer中
          totalBytesRead += readCount;
          emitCompleteSegments();//在把数据从buffer写入文件
        }
        return totalBytesRead;
      }
    

    调用了source.read()将数据读到BufferedSink的buffer中

      @Override public long read(Buffer sink, long byteCount) {
        if (sink == null) throw new IllegalArgumentException("sink == null");
        if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
        if (size == 0) return -1L;
        if (byteCount > size) byteCount = size;
        sink.write(this, byteCount);
        return byteCount;
      }
    

    调用输出流buffer.write()将数据写入,前方高能重点来了

      @Override public void write(Buffer source, long byteCount) {
    		if (source == null) throw new IllegalArgumentException("source == null");
        if (source == this) throw new IllegalArgumentException("source == this");
        checkOffsetAndCount(source.size, 0, byteCount);
    
        while (byteCount > 0) {
          if (byteCount < (source.head.limit - source.head.pos)) {//如果只需要当前Segment部分数据,或者说读到最后一个Segment了
            Segment tail = head != null ? head.prev : null;
            if (tail != null && tail.owner
                && (byteCount + tail.limit - (tail.shared ? 0 : tail.pos) <= Segment.SIZE)) {//如果要写的数据byteCount+limit+(如果当前不是共享Segment,则可以覆盖前面已读数据所以+pos,如果是共享Segment则不能覆盖已读数据所以为0)<= Segment.SIZE,证明写Buffer的Segment装得下
              source.head.writeTo(tail, (int) byteCount);//把读buffer中Segment数据写到Segment中
              source.size -= byteCount;
              size += byteCount;
              return;//return
            } else {//如果装不下则分割读buffer的Segment为两个,将我们需要数据的部分放在头部
              source.head = source.head.split((int) byteCount);
            }
          }
    
          //下面就是把读buffer中segment从链表取出,拼接到写buffer的segment链表中
          Segment segmentToMove = source.head;
          long movedByteCount = segmentToMove.limit - segmentToMove.pos;
          source.head = segmentToMove.pop();//从读buffer中断链
          if (head == null) {
            head = segmentToMove;
            head.next = head.prev = head;
          } else {
            Segment tail = head.prev;
            tail = tail.push(segmentToMove);//拼接到写buffer的链表中
            tail.compact();//尝试压缩Segment数据,移除多余Segment对象
          }
          source.size -= movedByteCount;
          size += movedByteCount;
          byteCount -= movedByteCount;
        }
      }
    

    接下来我们梳理下上面的流程

    1. 先判断当前是不是读到最后一个Segment了,如果是在判断写buffer中最后一个Segment写不写的下,写的下的话就写入然后return结束了,否则把要读的那个Segment拆分成两段,将我们需要数据的部分放在头部
    2. 如果第一个判断中没return,证明有整段的Segment数据需要拷贝,为了提高效率则直接将读buffer中Segment脱链,直接接到写buffer中提高效率,然后尝试压缩Segment链表的数据,移除多余的Segment

    流程说了,接下来我们看上面没分析的几个方法

    • source.head.writeTo(tail, (int) byteCount)
    • source.head.split((int) byteCount)
    • tail.compact()

    先看source.head.writeTo(tail, (int) byteCount)

      public void writeTo(Segment sink, int byteCount) {
        if (!sink.owner) throw new IllegalArgumentException();
        if (sink.limit + byteCount > SIZE) {//如果limit+byteCount>Size即装不下所以需要把数据往前移覆盖已读数据
          if (sink.shared) throw new IllegalArgumentException();//当前Segment是共享数组的则报错,至于shared属性在split方法的时候会说
          if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
          System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);//将数据向前移覆盖已读数据腾出空间
          sink.limit -= sink.pos;
          sink.pos = 0;
        }
    
        System.arraycopy(data, pos, sink.data, sink.limit, byteCount);//数据写入
        sink.limit += byteCount;
        pos += byteCount;
      }
    

    可以看到Segment#writeTo()是将数据写入传入的Segment中,如果sink写不下的话则会将已读数据覆盖腾出空间在写入。

    再来source.head.split((int) byteCount)

      public Segment split(int byteCount) {
        if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
        Segment prefix;
    
        if (byteCount >= SHARE_MINIMUM) {//如果要分割的数据量大于1024即1K则共享数组而不是数据拷贝
          prefix = sharedCopy();//共享数组
        } else {//数据量小于1K
          prefix = SegmentPool.take();//创建一个新的Segment
          System.arraycopy(data, pos, prefix.data, 0, byteCount);//拷贝数据
        }
    
        prefix.limit = prefix.pos + byteCount;
        pos += byteCount;
        prev.push(prefix);//插入分割出的Segment
        return prefix;
      }
    
      Segment sharedCopy() {
        shared = true;
        return new Segment(data, pos, limit, true, false);//共享同一个data数组
      }
    

    split则是先判断要分割的数据量大于1K么,如果大于则使用共享数组的方式创建Segment减少拷贝,否则创建一个新的Segment通过数组拷贝的方式将数据传入。

    而前面判断的shared就是这里赋值的,使用数组共享方式创建的Segment,是不能为了写入数据将数据前移覆盖已读数据腾出位置,因为持有的是数组引用会影响到别的Segment。

    最后tail.compact()

      public void compact() {
        if (prev == this) throw new IllegalStateException();
        if (!prev.owner) return; // Cannot compact: prev isn't writable.
        int byteCount = limit - pos;//计算当前Segment数据量
        int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
        if (byteCount > availableByteCount) return; //判断前一个Segment装得下么,装不下return
        writeTo(prev, byteCount);//将当前Segment数据写到前一个Segment中
        pop();//将当前Segment脱链
        SegmentPool.recycle(this);//回收
      }
    

    判断当前Segment中数据能放到前一个Segment么,如果可以则将数据写入,移除当前Segment。

    ok这里总结下Buffer的精髓操作

    对于输入流流到输出流是将输入流Buffer的数据直接转移到输出流Buffer中,转移分为两种情况

    1. 整段的Segment数据转移则是直接从输出Buffer中脱链然后插入输出Buffer中,直接修改指针效率非常高
    2. 非整段的Segment数据转移是判断输出Buffer最后一个Segment是否写的下,写的下的话是数组拷贝,写不下的话则将输入Segment一分为二,将要转移数据的Segment放第一个,然后按照1方式整段Segment转移到写buffer中

    并且对于Segment分割的时候有做优化,当需要转移数据量小于1K的时候是通过数组拷贝的方式将数据写到新Segment中,大于1K的时候是共享同一个数组,只是修改pos和limit来控制读取区间。

    4. Timeout超时处理

    okio的超时分为两种

    1. 同步超时TimeOut
    2. 异步超时AsyncTimeout

    4.1 同步超时TimeOut

    同步超时比较简单前面看到过,以写为例

      public static Sink sink(OutputStream out) {
        return sink(out, new Timeout());//创建同步超时对象
      }
    
      private static Sink sink(final OutputStream out, final Timeout timeout) {
        if (out == null) throw new IllegalArgumentException("out == null");
        if (timeout == null) throw new IllegalArgumentException("timeout == null");
    
        return new Sink() {
          @Override public void write(Buffer source, long byteCount) throws IOException {
            checkOffsetAndCount(source.size, 0, byteCount);
            while (byteCount > 0) {
              timeout.throwIfReached();//每次写的时候判断是否超时
              Segment head = source.head;
              int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
              out.write(head.data, head.pos, toCopy);
    
              head.pos += toCopy;
              byteCount -= toCopy;
              source.size -= toCopy;
    
              if (head.pos == head.limit) {
                source.head = head.pop();
                SegmentPool.recycle(head);
              }
            }
          }
        };
      }
    

    在每次写之前调用throwIfReached()方法检测是否超时

      public void throwIfReached() throws IOException {
        if (Thread.interrupted()) {
          throw new InterruptedIOException("thread interrupted");
        }
    
        if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
          throw new InterruptedIOException("deadline reached");
        }
      }
    

    如果超时则抛出异常,当然如果循环中某次写操作进行了很长时间是没法立即停止的,只能等到下次while循环的时候才会抛出异常停止,很明显这样是有缺陷的,于是还有异步超时。

    4.2 异步超时AsyncTimeout

    Okio给通过Socket方式创建的流提供的就是异步超时处理,以写操作为例

      public static Sink sink(Socket socket) throws IOException {
        if (socket == null) throw new IllegalArgumentException("socket == null");
        if (socket.getOutputStream() == null) throw new IOException("socket's output stream == null");
        AsyncTimeout timeout = timeout(socket);//创建了一个异步超时对象
        Sink sink = sink(socket.getOutputStream(), timeout);
        return timeout.sink(sink);//用AsyncTimeout.sink给sink包装了一层
      }
    

    再看到AsyncTimeout#sink

      public final Sink sink(final Sink sink) {
        return new Sink() {
          @Override public void write(Buffer source, long byteCount) throws IOException {
            checkOffsetAndCount(source.size, 0, byteCount);
              
              boolean throwOnTimeout = false;
              enter();//在进行写操作的时候会调用一下enter方法
              try {
                sink.write(source, toWrite);
                byteCount -= toWrite;
                throwOnTimeout = true;
              } catch (IOException e) {
                throw exit(e);
              } finally {
                exit(throwOnTimeout);//结束的时候会调用exit
              }
            }
          }
    

    再看超时开始的enter()和结束的exit()

      public final void enter() {
        if (inQueue) throw new IllegalStateException("Unbalanced enter/exit");
        long timeoutNanos = timeoutNanos();//超时时间
        boolean hasDeadline = hasDeadline();//是否有设置截止时间
        if (timeoutNanos == 0 && !hasDeadline) {
          return; // No timeout and no deadline? Don't bother with the queue.
        }
        inQueue = true;
        scheduleTimeout(this, timeoutNanos, hasDeadline);//开始超时任务
      }
    
      private static synchronized void scheduleTimeout(
          AsyncTimeout node, long timeoutNanos, boolean hasDeadline) {
        if (head == null) {//如果head为null创建一个AsyncTimeout作为head站个位置没有实际意义
          head = new AsyncTimeout();
          new Watchdog().start();//开启一个Watchdog线程进行超时监听
        }
    
        long now = System.nanoTime();
        //计算超时时间赋值给timeoutAt属性
        if (timeoutNanos != 0 && hasDeadline) {
          node.timeoutAt = now + Math.min(timeoutNanos, node.deadlineNanoTime() - now);
        } else if (timeoutNanos != 0) {
          node.timeoutAt = now + timeoutNanos;
        } else if (hasDeadline) {
          node.timeoutAt = node.deadlineNanoTime();
        } else {
          throw new AssertionError();
        }
    
        
        long remainingNanos = node.remainingNanos(now);//获取超时剩余时间
        for (AsyncTimeout prev = head; true; prev = prev.next) {//遍历链表将剩余时间少的排在链表头部
          if (prev.next == null || remainingNanos < prev.next.remainingNanos(now)) {
            node.next = prev.next;
            prev.next = node;
            if (prev == head) {//如果是第一次notify唤醒watchdog
              AsyncTimeout.class.notify();
            }
            break;
          }
        }
      }
    

    那梳理下上面代码就是将AsyncTimeOut根据剩余超时时间排序,即将超时的排在链表头部,然后启动了一个WatchDog线程去检查超时情况,接下来看看WatchDog

      private static final class Watchdog extends Thread {
        Watchdog() {
          super("Okio Watchdog");
          setDaemon(true);
        }
    
        public void run() {
          while (true) {
            try {
              AsyncTimeout timedOut;
              synchronized (AsyncTimeout.class) {
                timedOut = awaitTimeout();//获取即将超时的AsyncTimeout
    
               
                if (timedOut == null) continue;
                if (timedOut == head) {//如果获取到的为head代表超时队列没有任务了return
                  head = null;
                  return;//return
                }
              }
    
              
              timedOut.timedOut();//调用timeout.timeout()方法
            } catch (InterruptedException ignored) {
            }
          }
        }
      }
    

    WatchDog则是通过awaitTimeout获取即将超时的AsyncTimeout对象,如果AsyncTimeout为head则代表队列中没有任务了可以return,否则执行AsyncTimeout.timeout()方法触发超时

      static @Nullable AsyncTimeout awaitTimeout() throws InterruptedException {
        AsyncTimeout node = head.next;//拿到即将超时的AsyncTimeout
        if (node == null) {
          long startNanos = System.nanoTime();
          AsyncTimeout.class.wait(IDLE_TIMEOUT_MILLIS);
          return head.next == null && (System.nanoTime() - startNanos) >= IDLE_TIMEOUT_NANOS
              ? head  // The idle timeout elapsed.
              : null; // The situation has changed.
        }
    
        long waitNanos = node.remainingNanos(System.nanoTime());//获取剩余超时时间
        if (waitNanos > 0) {//如果大于0
          // Waiting is made complicated by the fact that we work in nanoseconds,
          // but the API wants (millis, nanos) in two arguments.
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          AsyncTimeout.class.wait(waitMillis, (int) waitNanos);//wait剩余超时时间
          return null;
        }
    
       
        head.next = node.next;
        node.next = null;
        return node;
      }
    }
    

    awaitTimeout是获取即将超时的AsyncTimeout,如果剩余超时时间>0,则wait剩余超时时间,否则返回超时的AsyncTimeout,而对于Socket,OKio给了默认实现。

    @Override protected void timedOut() {
            try {
              socket.close();
            } catch (Exception e) {
              logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
            } catch (AssertionError e) {
              if (isAndroidGetsocknameError(e)) {
                // Catch this exception due to a Firmware issue up to android 4.2.2
                // https://code.google.com/p/android/issues/detail?id=54072
                logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
              } else {
                throw e;
              }
            }
          }
    

    timeout的时候会关闭socket,在看到取消超时任务的方法exit()

      final void exit(boolean throwOnTimeout) throws IOException {
        boolean timedOut = exit();
        if (timedOut && throwOnTimeout) throw newTimeoutException(null);
      }
    
      public final boolean exit() {
        if (!inQueue) return false;
        inQueue = false;
        return cancelScheduledTimeout(this);//取消超时任务
      }
    
      private static synchronized boolean cancelScheduledTimeout(AsyncTimeout node) {
        // Remove the node from the linked list.
        for (AsyncTimeout prev = head; prev != null; prev = prev.next) {//从节点中删除AsyncTimeout
          if (prev.next == node) {
            prev.next = node.next;
            node.next = null;
            return false;//删除成功则代表未超时
          }
        }
    
        return true;//没有找到该节点则超时
      }
    

    exit则是从超时队列即AsyncTimeout链表中删除该节点,如果删除成功代表未超时,删除失败代表超时了,则会执行newTimeoutException(null)方法。提供个Socket的默认实现是抛出一个SocketTimeoutException

    @Override protected IOException newTimeoutException(@Nullable IOException cause) {
            InterruptedIOException ioe = new SocketTimeoutException("timeout");
            if (cause != null) {
              ioe.initCause(cause);
            }
            return ioe;
          }
    

    那么接下来我们总结下异步超时,以Socket创建流为例,在创建流的时候会创建一个AsyncTimeout对象,并包装生成的流对象,在进行操作的时候调用enter()方法开始超时任务,结束的调用exit()取消超时任务,内部则是将AsyncTimeout以超时时间进行排序,即将超时的排在前面,然后起一个WatchDog线程,从头部开始获取即将超时AsyncTimeout,如果还未超时则wait剩余超时时间,超时了的话则从队列中移除并调用timeout()方法,在exit()的时候则是看队列中是否还有AsyncTimeout对象,如果有代表未超时,否则超时了调用newTimeoutException()抛出一个异常停止操作。

    展开全文
  • okhttp3.3.1加okio1.8.0

    2018-12-06 16:31:12
    okhttp3.3.1加okio1.8.0,okhttp3 jar包 android,okhttp3.3.1和okio1.8.0
  • okhttp&okio.zip

    2020-02-17 21:44:30
    okhttp-3.4.1加 okio-1.9.0 Jar包,okhttp与okio出现版本适配冲突问题一直非常令人厌烦,本套Jar包经上传者实际运用,无版本冲突问题,请下载压缩包后解压使用。
  • okhttp-okio.zip

    2021-04-30 15:19:01
    okhttp-2.5.0,okhttp-3.0.1,okhttp-3.2.0,okio-1.6.0.jar多个关于okhttp的jar包,总有一个适合你
  • Android代码-okio

    2019-08-06 03:48:57
    Okio Okio is a library that complements java.io and java.nio to make it much easier to access, store, and process your data. It started as a component of OkHttp, the capable ...
  • okhttp-okio jar 包下载

    2020-07-16 10:30:51
    包含okhttp-3.9.0.jar和okio-1.13.0.jar,有需要的可以下载 包含okhttp-3.9.0.jar和okio-1.13.0.jar,有需要的可以下载
  • Okio源码分析

    万次阅读 2017-02-16 17:24:54
    1. okio概念 okio是一个由square公司开发的开源库,它弥补了Java.io和java.nio的不足,能够更方便快速的读取、存储和处理数据。 okio有自己的流类型Source和Sink,对应于java.io的InputStream和OutputStream。 okio...
  • Android Okio

    2017-07-13 18:14:17
    转自: Android 善用Okio简化处理I/O操作 Okio库是一个由square公司开发的,它补充了Java.io和java.nio的不足,以便能够更加方便,快速的访问、存储和处理你的数据。而OkHttp的底层也使用该库作为支持。而...
  • okhttp + okio

    2016-05-25 20:10:15
    okhttp的jar包和 okio.jar包,需要的可以下载。
  • okio-1.6.0.jar

    2020-10-28 16:06:48
    网络请求时需要依赖okio.jar这个包,不然出现:Exception in thread "main" java.lang.NoClassDefFoundError: okio/ByteString
  • OkIo介绍

    2017-05-12 17:18:09
    简介:上一篇介绍了okhttp3的设计原理,大家也知道okhttp3必须和okio一起使用,所以本篇介绍一下okio的设计原理,及设计优势。 Okio有两个好处, 1.装饰模式的设计,使得扩展更加方便 2.Segment的设计,是的...
  • OKHttp+Okio jar包

    2018-11-15 19:54:00
    okhttp3.0.1 + okio1.6.0 ,亲测android studio,eclipse可用
  • Okio简介

    2019-06-12 17:59:43
    一、简介 ...Okio的使用非常方便,相比java.io库它简化了很多繁杂的东西。这其中最核心的两个类: Sink与Source代表的分别就是传统的输出流与输入流。 1、写数据到文件 private void writeFile...
  • okio-1.12jar

    2018-06-09 19:56:22
    okio-1.12jar适用于使用okhttp 来进行网络编程android网络框架之OKhttp [1] 一个处理网络请求的开源项目,是安卓端最火热的轻量级框架,由移动支付Square公司贡献(该公司还贡献了Picasso) [2] 用于替代...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,010
精华内容 3,204
关键字:

okio