Okio实现过程分析「建议收藏」

Okio实现过程分析「建议收藏」一.Okio是什么文档介绍地址:https://square.github.io/okio/github地址:https://github.com/square/okioOkio是java.io和java.nio的一个补充库,使访问、存储和处理数据更加容易。包含两部分:ByteStrings和BuffersBysteString:是一个不可变的字节序列,可以看做Sring丢失已久的兄弟。它很容的将字节编码或解码为hex、base64和UTF-8;Buffer:可变的字节序列,像ArrayL

大家好,又见面了,我是你们的朋友全栈君。

一.Okio是什么

文档介绍地址:https://square.github.io/okio/

github地址:https://github.com/square/okio

Okio是java.io和java.nio的一个补充库,使访问、存储和处理数据更加容易。

包含两部分:ByteStrings 和 Buffers
BysteString:是一个不可变的字节序列,可以看做Sring丢失已久的兄弟。它很容的将字节编码或解码为hex、base64 和UTF-8;

Buffer:可变的字节序列,像ArrayList一样,不需要一些设置大小。

二.Okio实现过程分析

Okio的主要类结构如下,主要工作是为了更好的处理IO操作
image

Sink 文件写入操作实现:

以下代码基于okio:1.17.2版本进行分析

下面先看一个简单的代码示例,使用Sink进行写操作

private void testWrite() throws IOException {
    File file = getTargetFile();
    try(BufferedSink sink = Okio.buffer(Okio.sink(file))) {
        for(Map.Entry<String,String> entry : System.getenv().entrySet()){
            sink.writeUtf8(entry.getValue())
                    .writeUtf8("=")
                    .writeUtf8(entry.getValue())
                    .writeUtf8("\n");
        }
        sink.writeUtf8("").writeUtf8("\n");
    }
}

第一步:创建sink对象;根据传入的File创建FileOutputStream(),再创建Sink对象,实现具体的写入操作

  public static Sink sink(File file) throws FileNotFoundException {
    if (file == null) throw new IllegalArgumentException("file == null");
    return sink(new FileOutputStream(file));
  }
  
  //使用java的 OutputStream完成最终的数据写入操作
  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();
          //取出buffer的segment
          Segment head = source.head;
          int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
          out.write(head.data, head.pos, toCopy);
          //改变head的使用位置
          head.pos += toCopy;
          byteCount -= toCopy;
          source.size -= toCopy;
          //一个segment使用完成,回收到SegmentPool    
          if (head.pos == head.limit) {
            source.head = head.pop();
            SegmentPool.recycle(head);
          }
        }
      }

      @Override public void flush() throws IOException {
        out.flush();
      }

      ......
    };
  }  

第二步:创建BufferedSink对象,BufferedSink是一个抽象类,具体的实现有RealBufferedSink完成

BufferedSink sink = Okio.buffer(Okio.sink(file));
//RealBufferedSink是具体的实现类,
  public static BufferedSink buffer(Sink sink) {
    return new RealBufferedSink(sink);
  }
  
final class RealBufferedSink implements BufferedSink {
  public final Buffer buffer = new Buffer();
  public final Sink sink;
  boolean closed;
  
  RealBufferedSink(Sink sink) {
    if (sink == null) throw new NullPointerException("sink == null");
    this.sink = sink;
  }  
  
  ...
  @Override public BufferedSink writeUtf8(String string) throws IOException {
    if (closed) throw new IllegalStateException("closed");
    //向Segment写入数据由Buffer完成
    buffer.writeUtf8(string);
    return emitCompleteSegments();
  }  
  
  @Override public BufferedSink emitCompleteSegments() throws IOException {
    if (closed) throw new IllegalStateException("closed");
    long byteCount = buffer.completeSegmentByteCount();
    //这里调用okio创建的Sink对象的sink方法,由OutputStream写入数据
    //先将数据写入到Buffer,再由具体的实现执行写入操作,这样更易扩展
    if (byteCount > 0) sink.write(buffer, byteCount);
    return this;
  }  
  ...
  
}  
  

第三步:Buffered的写入数据操作,写入数据完成后,调用RealBufferedSink的emitCompleteSegments()方法,将具体的数据写入文件中

  @Override public Buffer writeUtf8(String string, int beginIndex, int endIndex) {
    ......

    // Transcode a UTF-16 Java String to UTF-8 bytes.
    for (int i = beginIndex; i < endIndex;) {
      int c = string.charAt(i);
      if (c < 0x80) {
      //获得一个可以写入的Segment
        Segment tail = writableSegment(1);
        ......

      } else if (c < 0x800) {
        // Emit a 11-bit character with 2 bytes.
       ......
      } else if (c < 0xd800 || c > 0xdfff) {
        // Emit a 16-bit character with 3 bytes.
        ......

      } else {
        .....
      }
    }

整个流程和平时我们操作不同的地方在于首先将数据写入到Buffer,最后再由实际的FileOutputStream写入到文件。

这里Segment的使用使用说明一下:

final class Segment{
    
 static final int SIZE = 8192;
  final byte[] data;
  //下一个可以读取的位置
  /** The next byte of application data byte to read in this segment. */
  int pos;

 //下一个可以写入的位置
  /** The first byte of available data ready to be written to. */
  int limit;
  
  ......
  ......
    
}

limit每次写入数据后,会像后移动对应的写入大小,pos每次读取数据后,向后移动对应读取数据大小,第一次写入8个byte后位置如下图:

image

当读取2个byte后位置如下图:

image

Source 的实现过程

三.Okio如何做到性能更优

空间换时间

使用SegmentPool缓存Segment,同时也是为了缓存byte[],减少频繁的创建byte[]数组导致gc,引起内存抖动,影响体验。

在内部,ByteStrig和Buffer做了一些事来节省CPU和内存

Buffer以分段的链表实现,当数据从一个buffer移动到另一个buffer的时,他会重新分配segment的所有权,而不是跨段复制数据,这种方法对于多线程程序特别有用,一个网络线程可以和工作线程进行交互数据,而不需要任何的拷贝

Sources and Sinks

Souce 对应 InputStream,Sink 对应 OutputStream。但是有以下方面的不同:

  • Timeouts. 该流提供了底层的访问超市机制。对于java.io的 socket流,read()和write()都调timeouts.
  • 易于实现.的ouce 申明了三个方法,read(),close()和timeout()。没有像available()或单字节读取这样的危险会导致正确性和性能上的意外。
  • 易使用.可使用BufferdSouce和BufferedSink提供的丰富API满足日常需求。BufferedSource使用了缓存,能够在更少的I/O情况下,做更多的事情。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/134674.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号