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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Json字符串和对象相互转换[通俗易懂]

    Json字符串和对象相互转换[通俗易懂]importcom.fasterxml.jackson.databind.ObjectMapper;publicclassJsonUtil{privatestaticfinalObjectMapperMAPPER=newObjectMapper();/***把对象转字符串*@paramdata*@return*/publicstaticStringobjectToJson(Objec

    2025年5月24日
    1
  • 树莓派4B CPU 挖矿 比特币(树莓派4b cpu)

    在使用空闲的服务器(VPS)来进行挖矿(门罗币XMR)中,我们用服务器来挖矿,如果你没有服务器,那么用手头的树莓派也可以来挖矿。我用树莓派搭了一个DNS服务器,CPU利用率很小,因此我决定在树莓派上再搭建一个挖矿软件进行挖矿。原因有两个,一是树莓派功耗低,可以长期开机,二是用来学习,虽然以树莓派的算力,肯定挖不了什么,但也算用来学习数字货币了。以下是用树莓派来挖莱特币LTC的教程。首先注册莱特币L…

    2022年4月12日
    108
  • python里字符串转数字_c 字符串转数字

    python里字符串转数字_c 字符串转数字python数字转字符串的实现方法:1、使用格式化字符串,语句如“tt=322tem=’%d’%tt”;2、通过“str(5)”方法实现转换。推荐:《python教程》数字转成字符串方法一:使用格式化字符串:tt=322tem=’%d’%tttem即为tt转换成的字符串常用的格式化字符串:%d整数%f%F浮点数%e%E科学计数%g%Ge和%f/%E和%F的简写%%输出%格式化…

    2022年10月12日
    2
  • PHP工厂模式的好处

    PHP工厂模式的好处 顾名思义,工厂是可以加工零件的,PHP程序中的工厂模式也有相同的功能,可以方便的使用一个静态的工厂方法来实例化某一个类,那么这样做的好处是什么呢?初学PHP的设计模式,以下是我个人的理解 一般我们实例化一个类会给它一些参数以便在其构析的时候可以根据不同的参数反馈出我们需要的结果。举例说明,以下是一个User类,非常简单:01.02.      03.    int

    2022年7月25日
    13
  • vim复制粘贴命令

    vim复制粘贴命令1.选定文本块。使用v进入可视模式,移动光标键选定内容。 2.复制的命令是y,即yank(提起),常用的命令如下:   y   在使用v模式选定了某一块的时候,复制选定块到缓冲区用;   yy  复制整行(nyy或者yny,复制n行,n为数字);   y^  复制当前到行头的内容;   y$  复制当前到行尾的内容;   yw  复制一个word(ny…

    2022年9月23日
    5
  • 【USB】全球USB厂家 USB ID大全。更新时间:2017-07-29[通俗易懂]

    【USB】全球USB厂家 USB ID大全。更新时间:2017-07-29[通俗易懂]## ListofUSBID’s## MaintainedbyStephenJ.Gowdy# Ifyouhaveanynewentries,pleasesubmitthemvia# http://www.linux-usb.org/usb-ids.html# orsendentriesaspatches(diff-uoldnew)i

    2022年7月12日
    132

发表回复

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

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