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


相关推荐

  • 服务降级的概念及应用手段

    服务降级的概念及应用手段什么是服务降级服务降级,就是对不怎么重要的服务进行低优先级的处理。说白了,就是尽可能的把系统资源让给优先级高的服务。资源有限,而请求是无限的。如果在并发高峰期,不做服务降级处理,一方面肯定会影响整体服务的性能,严重的话可能会导致宕机某些重要的服务不可用。所以,一般在高峰期,为了保证网站核心功能服务的可用性,都要对某些服务降级处理。服务降级手段拒绝服务判断应用来源,高峰时段拒

    2022年5月25日
    40
  • linux怎么配置tomcat的端口,linux下安装tomcat及iptables开放端口

    linux怎么配置tomcat的端口,linux下安装tomcat及iptables开放端口在Linux运行Tomcat之前必须安装jdk,并配置环境变量。以下是Tomcat的安装配置:1.下载安装包解压以tomcat7为例,https://tomcat.apache.org/download-70.cgi,下载apache-tomcat-7.0.88.tar.gz即可可在/usr下建以tomcat目录,将安装包解压在此目录下即可tar-zxv-fapache-tomcat-7.0…

    2022年5月15日
    36
  • vim 常用命令

    vim 常用命令普通命令模式复制内容功能需求操作说明复制全部文本按下ggyG复制完成,注意:G必须大写粘贴内容命令操作说明p通过箭头移动光标到需要粘贴的位置,按下p将内容粘贴到光标后面P按下P将内容粘贴到光标前面删除内容功能需求操作说明D删除游标所在位置的字符到行尾全选文本按下ggvG全选文本,但是最后一行仅选中第一个字符,按下ggVG才是真正的全选文本删除按下dG删除光标所在行以及下面的

    2022年5月9日
    40
  • 下载whl文件,离线方式安装numpy包_python离线安装pip

    下载whl文件,离线方式安装numpy包_python离线安装pip一:单独下载文件1、下载whl离线文件到本地,放到c盘根目录(任意位置均可,只是方便安装)https://pypi.org/https://www.lfd.uci.edu/~gohlke/pythonlibs/(推荐用这个地址下载whl文件,国内源,速度快。ctrl+f找到自己需要的文件)2、cmd到存放whl文件的目录3、pip安装whl离线文件pipinstall****.whl(****.whl是我们下载的whl的文件名称)二、批量下载…

    2022年8月30日
    4
  • 剑指 Offer:Redis 的 14 大使用场景!

    剑指 Offer:Redis 的 14 大使用场景!

    2022年2月11日
    56
  • 超详细总结之Word2Vec(一)原理推导[通俗易懂]

    超详细总结之Word2Vec(一)原理推导[通俗易懂]本章是介绍Word2Vec的原理推导部分,后面还会有基于TensorFlow的Word2Vec代码实现讲解。一、什么是Word2Vec?2013年,Google团队发表了word2vec工具。word2vec工具主要包含两个模型:跳字模型(skip-gram)和连续词袋模型(continuousbagofwords,简称CBOW),以及两种高效训练的方法:负采样(negatives…

    2022年5月17日
    33

发表回复

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

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