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情况下,做更多的事情。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Camera 之水波纹和banding现象[通俗易懂]

    Camera 之水波纹和banding现象[通俗易懂]预览画面中出现了一条明一条暗相间隔的竖条纹,这种现象叫做“水波纹”,并对原因进行了讲解,现记录如下。其实这些“水波纹”产生是因为手机的快门频率与灯光的频率不匹配导致的。首先,我们都知道手机拍照的时候都是有一定曝光时间的,例如假设手机的快门频率为50Hz,则其拍照时的曝光时间就是20ms。同理,屏幕或者日光灯不是一直在发光的,而是更隔一段时间就会刷新一次,我们生活中的日光灯为50Hz,国外的是60Hz。例如那个50Hz,就代表每秒刷新50次,因为刷…

    2022年10月13日
    0
  • sql报错将截断字符串或二进制数据_sql根据分隔符截取字符串

    sql报错将截断字符串或二进制数据_sql根据分隔符截取字符串今天使用数据库的时候,遇见这样的错误:成因分析:自己在设计数据库的时候,将表的某些属性的域的长度设置的小了:而我在填写的对应的数据长度是超过了数据库属性长度的设计,这样,在将数据录入数据库的时候,会将数据截断。解决方案:扩充数据库对应属性的长度:~~~~~~~~~~完美解决了~~~~~~~~~~~~~~~~~~~~

    2022年10月7日
    4
  • VB学习1

    VB学习1 

    2022年6月21日
    31
  • python激活码2021四月【在线注册码/序列号/破解码】

    python激活码2021四月【在线注册码/序列号/破解码】,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月19日
    60
  • autojs实现的微信消息推送源代码免费分享

    autojs实现的微信消息推送源代码免费分享说明本文提供的代码仅供参考。不建议用于生产环境。可能有些地方在最新版本的Auto.js上面需要做修改,才能运行。Auto.js简介Auto.js是利用安卓系统的“辅助功能”实现类似于按键精灵一样,可以通过代码模拟一系列界面动作的辅助工作。与“按键精灵”不同的是,它的模拟动作并不是简单的使用在界面定坐标点来实现,而是类似与win一般,找窗口句柄来实现的。Auto.js使用JavaScri…

    2022年6月3日
    142
  • jq tmpl输出编码html,jQuery tmpl 讲解「建议收藏」

    jq tmpl输出编码html,jQuery tmpl 讲解「建议收藏」2016-07-0114:30陈铭竑1、什么是jQuery-tmpl(1)jQuery的一个类库(2)一个轻量级的前端模板引擎(vue.js也是一种前端模板引擎)(3)可以在模板中实现逻辑运算2、jQuery-tmpl的语法(1)占位:${变量}或{{=变量}}注:=和变量之间一定要有空格(2)循环{{each(i,obj)objs}}…{{/each}}(3)选择{{if条件}}….

    2022年6月16日
    43

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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