OKIO源码分析

本文深入剖析了OKIO的核心组件,包括Sink、Source、Buffer和ByteString的功能与实现原理。探讨了Segment如何优化数据传输效率,以及GzipSink、GzipSource等组件如何实现压缩与解压缩。

OKIO源码分析

概述

从okio在github上的README我们知道okio主要有四个东西,分别是Sink, Source,Buffer,ByteString
Sink:类似java中输出流OutputStream
- Source:类似java中的输入流InputStream
- Buffer:看名字就知道就是一个缓冲区,看到这个Buffer 是不是想起了我们平时写io流时都会创建一个byte[] 作为缓存区,写入时,将这个缓存区的数据写入流,读取时,将流中的数据读入这个缓存区。往下看就会知道Buffer 的内部其实就是byte 数组。
- ByteString:顾名思义,这个类跟byteString 有关。

Sink 和 Source

Sink, Source 都是接口,定义了一些行为,供子类实现。
这里写图片描述
可以看到它们的内部定义了非常简单的方法。也可以看出它们必须配合Buffer 来使用才行。
BufferSink 也是接口,继承了Sink 并提供了更多的write 方法使其能处理byte, short, String 等类型,同理BufferSource 也是接口,继承了Source 并提供了更多的read 方法。

Buffer

接下来就来看看实现了BufferSink和BufferSource 的类RealBufferdSink, RealBufferdSource。这两个类的内部write 方法或者 read 方法的实现都是通过Buffer 类,重头戏来了。
这里写图片描述
Buffer 类实现了BufferedSource, BufferdSink, Cloneable 接口,具有两个属性long size和 Segment headlong size 表示其内部所存储的数据的长度,它存储的数据其实本质上就是byte[]Segment 内部有一个大小为2048的byte[] 保存着Buffer 需要存储的数据。

Buffer 只能存储2048byte 么? 当然不是,Segment 的实现方式是一个循环双线链表,当一个Segment 存满了,往链尾添加一个新的Segment 就又可以存储更多的数据了。

那为什么不直接在Buffer 中写一个byte[] 来保存数据,还要弄一个什么循环链表Segment 来存储数据呢?答案就在数据的转移。当一个Source 要读取一个Sink 中的数据时,其实就是把数据从SourceBuffer 转移到SinkBuffer 中。此时如果Buffer 中是用byte[] 实现的,那么我们势必需要进行byte 数组拷贝的工作,但是用一个链表实现的情况下,我们可以直接把Sink 中的 Segment 节点的指针指到Source 中的Segment 链表尾就OK了,而不用进行数组拷贝,更高效。

Segment

上面收到Segment 是链表的实现方式, 分析该类就从Buffer 的方法wite(byte[] source, int offset, int byteCount) 开始吧。

@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;
    while (offset < limit) {
      Segment tail = writableSegment(1);

      int toCopy = Math.min(limit - offset, Segment.SIZE - tail.limit);
      System.arraycopy(source, offset, tail.data, tail.limit, toCopy);

      offset += toCopy;
      tail.limit += toCopy;
    }

    size += byteCount;
    return this;
  }

checkOffsetAndCount 方法就是检查下参数对不对。检查完下面有个while 循环将数据拷贝到Segment中,那该Segment 是怎么来的呢,我们看下writableSegment(int minmumCapacity) 方法。

Segment writableSegment(int minimumCapacity) {
    if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();

    if (head == null) {
      head = SegmentPool.take(); // Acquire a first segment.
      return head.next = head.prev = head;
    }

    Segment tail = head.prev;
    if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {
      tail = tail.push(SegmentPool.take()); // Append a new empty segment to fill up.
    }
    return tail;
  }

该方法根据你提供的容量返回一个能存的下你声明的容量的Segment 当作容器供你使用。当Buffer 的内部没有存储数据时,它会调用SegmentPool.take() 方法获取到一个SegmentSegmentPool 是一个Segment 的缓存池, 最多可以缓存64 * 1024KiB也就是32个Segment 。这种缓冲方式跟Message 很像。Buffer 的内部有存储数据时会先查看链尾节点的存储空间符不符合你的要求,符合就返回,不符合就调用Segment.push(Segment segment) 方法添加一个新的节点在末尾供你存储数据。 拿到Segment 容器后,就可以拷贝数据了,如此反复直到数据都写入到Buffer 中。

ForwardingSink、 ForwardingSource

这两个类网络上有人说它们是装饰着 但是我觉得它们更像是代理,用法就是继承它们,然后override它们的方法。

GzipSink、 GzipSource、 DeflaterSink、 InflaterSource

GzipSink和GzipSource 实现了gzip 压缩,解压缩。其实就是弄个头,体,尾。头跟尾详情见GZIP。 体的话是调用DeflaterSink跟InflaterSource 实现的。而这两个类的内部又是调用java的API Deflater和Inflater 类实现压缩和解压的。关于这两个类的使用方式,不必google了。直接看这两个的头部注释就够了,在这里我也贴出来吧。

Inflater用法

 byte[] compressedBytes = ...
     int decompressedByteCount = ... // From your format's metadata.
     Inflater inflater = new Inflater();
     inflater.setInput(compressedBytes, 0, compressedBytes.length);
     byte[] decompressedBytes = new byte[decompressedByteCount];
     if (inflater.inflate(decompressedBytes) != decompressedByteCount) {
         throw new AssertionError();
     }
     inflater.end();

Deflater用法

byte[] originalBytes = ...

     Deflater deflater = new Deflater();
     deflater.setInput(originalBytes);
     deflater.finish();

     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     byte[] buf = new byte[8192];
     while (!deflater.finished()) {
         int byteCount = deflater.deflate(buf);
         baos.write(buf, 0, byteCount);
     }
     deflater.end();

     byte[] compressedBytes = baos.toByteArray();

ByteString

说了半天还没有说ByteString 呢,看到这个名字就知道跟ByteString 有关。内部有一个byte[] 用来保存数据, 主要的作用就是将字符串转成byte[] 保存起来,并提供将byte[] 转成字符串的方法,也提供了一些将字符串加密编码的方法。

Okio

最后说下最常用的大Boss, Okio 类。我们使用Okio库都要用到这个类,该类是一个大工厂, 为我们创建出各种各样的SinkSource 对象。提供了三种数据源InputStream/OutputStreamSocketFile, 我们可以把本应该对这三类数据源的IO操作通过Okio库来实现,更方便,更高效。

说到这里也就差不多了,还有不明白的,还是去看下源码,也不多,也不难!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值