Okio源码分析

Okio是Square开源框架,优化了Android的IO操作,提供ByteString与Buffer,Source与Sink接口,实现高效数据读写。Okio的Segment和SegmentPool实现内存管理,超时机制避免阻塞,同时采用生产者/消费者模型。虽然不支持非阻塞IO,但在Java IO基础上进行了封装和优化。
摘要由CSDN通过智能技术生成

okioSquare开源框架之一,它对java.iojava.nio做了补充,使访问,存储和数据处理变得更加容易。它最早是Okhttp组件之一。
在这里插入图片描述

1、ByteString与Buffer

Okio主要围绕ByteStringBuffer这两个类展开,其主要功能都封装在这两个类中:

  • ByteString:是一个类似String的不可变类,它可以很容易的在byteString之间进行转换。该类提供了编/解码为hex,md5,base64及UTF-8等方法。
  • Buffer:是一个可变的字节序列。 与ArrayList一样,无需提前调整缓冲区大小。 Buffer内部维护了一个双向链表,从链表尾部写入数据,头部读取数据。

ByteStringBuffer做了一些节省CPU和内存的操作。 如果将一个字符串编码为ByteStringByteString就会缓存对该字符串的引用(以空间换时间),这样如果以后对其进行编/解码等操作,则无需在byteString之间进行转换。

  //字符串对应的字节数据,避免再一次转换
  final byte[] data;
  //字符串
  transient String utf8; // Lazily computed.

Buffer内部维护了一个以Segment为节点的双向链表。 当数据从一个Buffer移动到另一个Buffer时,仅需要进行一次数据拷贝,且它会重新分配Segment的所有权,而不是重新创建Segment对象。

2、Source与Sink

Okio包含自己的流类型,称为SourceSink,其工作方式虽然类似InputStreamOutputStream,但它与Java I/O相比具有以下优势(参考自Android学习笔记——Okio):

  • Okio实现了I/O读写的超时机制(Timeout),防止读写出错从而导致一直阻塞。
  • N合一,OKio精简了输入输出流的类个数
  • 低的CPU和内存消耗,引入SegmentSegmentPool复用机制
  • 使用方便。ByteString处理不变byteBuffer处理可变byte
  • 提供了一系列的工具。OKio支持md5、sha、base64等数据处理

SourceSink可以与InputStreamOutputStream互相操作。我们可以将任何Source视为InputStream,也可以将任何InputStream视为Source。同样适用于SinkInputStream

3、Okio数据读写流程

 前面简单介绍了Okio,下面就来看看如何使用。

    //okio实现图片复制
    public void copyImage(File sinkFile, File sourceFile) throws IOException {
   
        //try里面的代码是Okio的标准写法,不能改变
        try (Sink sink = Okio.sink(sinkFile);
             BufferedSink bufferedSink = Okio.buffer(sink);
             //从文件读取数据
             Source source = Okio.source(sourceFile);
             BufferedSource bufferedSource = Okio.buffer(source)) {
   
            //图片复制
            bufferedSink.write(bufferedSource.readByteArray());
            //设置超时时间为1秒中,
            sink.timeout().deadline(1, TimeUnit.SECONDS);
            //写入数据,将字符串以UTF-8格式写入,Okio专门针对utf-8做了处理
            bufferedSink.writeUtf8(entry.getKey())
                     .writeUtf8("=")
                     .writeUtf8(entry.getValue())
                     .writeUtf8("\n");
            //读取数据
            String str=bufferedSource.readUtf8();
            //读取数据并返回一个ByteString
            ByteStringstr=bufferedSource.readByteString();

        }
    }

 正如前面所说的那样,Okio使用起来非常方便。由于Java字符串采用的是UTF-16编码,而一般开发中使用的都是UTF-8编码,所以Okio对字符串编码做了特殊处理。

3.1、Okio读数据原理分析

Source的意思是水源,它对应着输入流,在Okio中通过Okio.source方法来获得一个Source对象。

  //在Okio这个类中关于source重载的方法还是蛮多的,这里以文件为例
  public static Source source(File file) throws FileNotFoundException {
   
    if (file == null) throw new IllegalArgumentException("file == null");
    return source(new FileInputStream(file));
  }
  public static Source source(InputStream in) {
   
    return source(in, new Timeout());
  }
  private static Source source(final InputStream in, final Timeout timeout) {
   
    ...
    //这里才是真正读去数据的地方
    return new Source() {
   
      @Override public long read(Buffer sink, long byteCount) throws IOException {
   
        ...
        try {
   
          //每次写数据时都先检查是否超时,默认未设置超时
          timeout.throwIfReached();
          //获取链表的尾节点
          Segment tail = sink.writableSegment(1);
          //由于每个Segment的SIZE为8KB,所以每一次拷贝不能超过这个值
          int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
          //通过InputStream读取数据
          int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
          //数据读取完毕
          if (bytesRead == -1) return -1;
          //可写取位置往后移
          tail.limit += bytesRead;
          //读取的总字节数
          sink.size += bytesRead;
          //返回当前读取的字节数
          return bytesRead;
        } catch (AssertionError e) {
   
          ...
        }
      }
      ...
    };
  }

 可以发现,这个的Source是一个匿名对象。得到Source对象后,通过Okio.buffer方法将该对象传递给BufferedSourceBufferedSource是一个接口,它的具体实现类是RealBufferedSource
 在上面例子中是调用RealBufferedSourcereadByteArray方法来读取数据,下面就来看这个方法的实现。

  //RealBufferedSource对应的Buffer 
  public final Buffer buffer = new Buffer();
  @Override public byte[] readByteArray() throws IOException {
   
    //将数据写入buffer
    buffer.writeAll(source);
    //将所有数据已字节数组形式返回
    return buffer.readByteArray();
  }

 在readByteArray方法中会首先将数据写入到Buffer中,并生成一个双向链表。

  @Override public long writeAll(Source source) throws IOException {
   
    if (source == null) throw new IllegalArgumentException("source == null");
    long totalBytesRead = 0;
    //这里的source就是前面在Okio中创建的匿名Source对象
    for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {
   
      totalBytesRead += readCount;
    }
    return totalBytesRead;
  }

 将数据写入Buffer后,调用BufferreadByteArray方法生成一个字节数组并返回。

    @Override
    public byte[] readByteArray() {
   
        try {
   
            //在读取数据时,就会得到size的大小
            return readByteArray(size);
        } catch (EOFException e) {
   
            throw new AssertionError(e);
       
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值