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月2日 下午10:00
下一篇 2022年5月2日 下午10:20


相关推荐

  • 渗透测试技术_Nessus工具(二) _漏洞扫描工具 Nessus的使用教程

    渗透测试技术_Nessus工具(二) _漏洞扫描工具 Nessus的使用教程漏洞扫描工具Nessus的使用教程1、Nessus使用教程1.1、Nessus登录在浏览器中访问:https://{服务器IP}:8834例如访问:https://10.1.1.191:8834/输入你注册的账号密码进行登录,例如:nessus_casb/liaxx,进入主页面。1.2、新建1个主机扫描1.2.1、点击右上角的”NewScan”新建一个扫描1.2.2、选择“BasicNetworkScan”,进行配置项目名称,对项目的描述,以及最重要的目标I.

    2022年10月18日
    5
  • java 实现atof函数

    java 实现atof函数atof 函数将字符串转换为浮点数 此函数规则如下 它会扫描参数 str 字符串 跳过前面的空白字符 例如空格 tab 缩进等 可以通过 isspace 函数来检测 直到遇上数字或正负符号才开始做转换 而再遇到非数字或字符串结束时 0 才结束转换 并将结果返回 参数 str 字符串可包含正负号 小数点或 E e 来表示指数部分 如 123 456 或 123e 2 返回值 返回转换后的浮点数

    2026年3月19日
    2
  • 怎样配置FTP服务器

    怎样配置FTP服务器配置 FTP 服务器 FTP 为文件传输协议 用于 Internet 上的控制文件的双向传输 要通过 FTP 来实现本地计算机与云服务器的文件传输 首先要在服务器上进行 FTP 的配置 下面以 Windowsserve 为例配置服务器的 FTP 一 Windows 系统 1 安装 FTP 服务器通过控制台连接实例 通过 开始 管理工具 服务器管理 找到并点击 服务器管理 右键点击服

    2026年3月18日
    2
  • load average信息详解

    load average信息详解一、什么是loadaverage?linux系统中的Load对当前CPU工作量的度量(WikiPedia:thesystemloadisameasureoftheamountofworkthatacomputersystemisdoing)。也有简单的说是进程队列的长度。LoadAverage就是一段时间(1分钟、5分钟、15分钟)内平均L

    2022年7月17日
    20
  • springcloud详细教程_史上最简单的画

    springcloud详细教程_史上最简单的画SpringCloudBus将分布式的节点和轻量的消息代理连接起来。这可以用于广播配置文件的更改或者其他的管理工作。一个关键的思想就是,消息总线可以为微服务做监控,也可以作为应用程序之间相互通讯。本文要讲述的是用AMQP实现通知微服务架构的配置文件的更改。一、准备工作本文还是基于上一篇文章来实现。按照官方文档,我们只需要在配置文件中配置spring-cloud-starter-bus-amq

    2025年7月26日
    6
  • 医院病历管理系统java版本二

    由于百度网盘分享容易被屏蔽,所以采取城通网盘分享,如果失效请私信我,如果侵权也私信我。下载地址:点击打开链接

    2022年4月9日
    40

发表回复

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

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