赵东阳
openharmony tpc
前不久,三方组件库上新了一批js/ets组件,其中就包括okio组件。okio是一个可应用于openatom openharmony(以下简称“openharmony”)的高效io库,它依托于系统能力,提供字符串的编解码转换能力,基础数据类型的读写能力以及支持对文件读写的能力。本期将为大家介绍okio的工作原理及使用方法。
一、okio的产生背景
io,即输入输出(input/output)。绝大多数应用都需要与外部进行数据交互,这就会涉及io。系统提供了io能力,在使用系统io时,通常需要一个中间缓冲区来保存读取到的数据。数据先从输入流缓冲区复制到中间缓冲区,再从中间缓冲区复制到输出流缓冲区。中间多次拷贝,降低了io效率,同时增加了系统消耗。为了满足开发者对io的更高要求,三方组件库推出io处理利器——okio(js版本)。okio使用segment作为数据存储容器,通过提供segment移动、共享、合并和分割的能力,让数据读写变得非常灵活,也减少了数据复制,提升了io效率。此外,okio还通过segmentpool对segment进行回收和复用,减少大量创建segment带来的系统消耗。下面就带大家深入了解js版本的okio的工作原理,探索它是如何提升io效率的。
二、两个基本概念
在深入解析okio的工作原理之前,我们先来了解两个基本概念:segment和segmentpool。 1. segmentokio将数据分割成一块块的片段存放在segment里面。segment是一个数据存储的真正类,内部维护着一个大小为8192字节的字节数组用于存储数据。segment最小可共享、可写入的数据大小为1024字节。segment使用pos、limit、shared、owner、prev、next来分别记录读写位置、是否可写入、是否能共享、数据拥有者、前置节点和后置节点信息。segment对外提供sharedcopy、unsharedcopy、split、push、pop、compact、writeto等接口用于操作数据。segment同时拥有前置节点和后置节点,构成一个双向链表。读取数据的时候,从双向链表的头部开始读取;而写入数据的时候,从双向链表的尾部写入数据。 2. segmentpool为了管理segment,okio维护了一个segment对象池(即segmentpool),对废弃的segment回收、复用和内存共享,从而减少内存的申请和gc(garbage collection,垃圾收集)的频率,使性能得到优化。segmentpool是一个由最多8个segment组成的单链表。一个segment的最大大小是8192字节(即8kb),所以segmentpool的最大大小是64kb。
三、okio的工作原理
okio组件最重要的功能就是“读”和“写”。下面我们就从读写开始,了解okio的工作原理。 1. 读写数据okio读写数据的过程中,遵循大块数据移动、小块数据复制的原则。okio从输入流读取数据到输入流缓冲区时,会先找到双向链表尾部的segment节点,如果此节点的剩余容量足够,则直接将读取到的数据存入到此节点。如果此节点的剩余容量不足,则从segmentpool里面取一个segment链接到双向链表的尾部,然后将数据存入这个新节点。okio从输入流缓冲区读取数据,再写入数据到输出流缓冲区。这个过程比较复杂,有以下几种情况:(1) 从输入流缓冲区获取到segment,如果数据是满的(字节数组data长度为8092字节),那么直接修改此segment的prev和next信息,将其添加到输出流缓冲区的双向链表的尾部,省去一次数据复制过程。
图1 大块数据移动 (2) 从输入流缓冲区获取到segment(假设为segment1),如果数据不是满的,可以通过pos和limit信息来确定segment1的可读数据,再和输出流缓冲区的双向链表的尾部节点(假设为segment2)的剩余容量进行对比: 如果segment1的可读数据比segment2的剩余容量小,则把segment1的数据复制到segment2,然后回收segment1到segmentpool。
如果segment1的可读数据比segment2的剩余容量大,那么直接修改segment1的prev和next信息,将其添加到segment2的后面。
(3) 从输入流缓冲区获取到segment(假设为segment3),如果只需要传递部分数据(比如总数据为4096字节,只传递1024字节),okio会通过split接口将segment3拆分成含3072字节数据的segment3-1和含1024字节数据的segment3-2,然后按照(2)的逻辑将segment3-2的数据写入输出流缓冲区。
图2 segment拆分
拆分segment的时候,可以通过参数指定拆分后的第一个segment含有的未读字节数(bytecount)。拆分后,第一个segment包含的数据范围是[pos,pos+bytecount),第二个segment包含的数据范围是[pos+bytecount,limit)。拆分segment时也遵循大块数据移动、小块数据复制的原则。当bytecount大于1024时,使用共享的segment,否则复制数据。
(注:文件、流、socket相关的io优化需要系统支持,待后续版本优化提供。)
2. segment的回收与复用
接下来,我们再来看看segmentpool是如何回收和复用segment的。
每次okio想要获取segment就从segmentpool中获取,使用完毕后又会放回到segmentpool中复用,核心方法为take()和recycle()。
(1)take()方法
take()方法负责从对象池单链表的头部获取可以使用的segment。如果获取不到,说明单链表是空的,此时新创建一个segment给缓冲区使用。如果能获取到,则取出单链表的头部节点,再将下一个节点置为单链表的头部节点,并将取出来的segment的next置空,同时更新对象池大小。
(2)recycle()方法
recycle()方法负责回收缓冲区里面使用完毕的segment。回收开始时,首先更新对象池大小,然后把回收对象segment添加到单链表头部,接着重置segment的pos和limit为0。注意,以下情况不会回收segment:
● 当前segment的prev和next不为空
● 当前segment是共享的
● 对象池已经有8个segment了
3. 字符串处理
除了segment和segmentpool外,okio还封装了bytestring类来进行字符串处理。bytestring提供base64编解码、utf-8编码、十六进制编解码、大小写转换、内容比较等丰富的api,可以很方便地处理字符串。
在进行字符串处理时,由于bytestring同时持有原始字符串和对应的字节数组,可以直接使用字节数组里面的数据进行操作,不需要先将字符串转换为字节数组。特别是在频繁转换编码的场景下,通过这种以空间换时间的方式,可以避免字符串与字节数组的多次转换,减少了时间和系统性能消耗。
四、okio的使用及示例
1. 前置配置 步骤一:在entry 的package.json文件中添加以下依赖项。
dependencies: { okio: ^1.0.0 } 步骤二:配置仓库镜像地址。 npm config set @ohos:registry=https://repo.harmonyos.com/npm/ 步骤三:deveco studio的terminal里面输入以下命令下载源代码。 cd entrynpm install @ohos/okio 步骤四:文件的头部引入okio库。 import okio from '@ohos/okio'; 步骤五:在config.json文件中申请存储权限。 reqpermissions: [ { name: ohos.permission.write_user_storage, //写入用户存储的权限 reason: storage, usedscene: { when: always, ability: [ com.example.okioapplication.mainability } }, { name: ohos.permission.read_user_storage, //读取用户存储的权限 reason: storage, usedscene: { when: always, ability: [ com.example.okioapplication.mainability ] } }, { name: ohos.permission.write_external_media_memory, //写入外部存储的权限 reason: storage, usedscene: { when: always, ability: [ com.example.okioapplication.mainability ] } } ] 2. 代码实现
执行完上面的配置操作后,就可以进入代码编写阶段了。开发者可以使用okio提供的丰富的api接口来实现功能。下面为大家展示四个实现示例,供大家参考学习。
示例1:文件写入和读取
本示例通过sink将内容写入文件,通过source从文件读取内容。代码如下:
//通过sink将内容写入文件var sink = new okio.sink(this.fileuri);sink.write(this.value,false); //通过source从文件读取内容var source = new okio.source(this.fileuri);source.read().then(function (data) { context.readvalue = data; }).catch(function (error) {console.log(error=>+error); }); 示例2:base64解码
本示例通过bytestring实现base64解码功能,代码如下:
let bytestringobj = new okio.bytestring.bytestring(''); //生成bytestring对象let decodebase64 = bytestringobj.decodebase64('sgvsbg8gd29ybgq='); //解码base64字符串this.decodebase64value = json.stringify(decodebase64); //显示解码结果 示例3:十六进制解码
本示例通过bytestring实现十六进制解码功能,代码如下:
let bytestringobj = new okio.bytestring.bytestring('');let decodehex = bytestringobj.decodehex('48656c6c6f20776f726c640d0a');this.decodehexvalue = json.stringify(decodehex); 示例4:utf8编码
本示例通过bytestring实现utf8编码功能,代码如下:
let bytestringobj = new okio.bytestring.bytestring('');let encodeutf8 = bytestringobj.encodeutf8('hello world #4 ( ͡ㆆ ͜ʖ ͡ㆆ)');this.encodeutf8value = json.stringify(encodeutf8); 本期okio组件就为大家介绍到这里了。okio组件已开源,欢迎大家参与贡献,开源地址如下:
https://gitee.com/openharmony-tpc/okio
保隆科技助力汽车智能网联产业化
开奖 | 不装了,就是超级能装!
在Raspberry Pi上安装Android的方法
企业如何快速构建基于音视频服务的业务
赛普拉斯全新EZ-PD CCG3控制器简化USB Type-C主机、配件以及电源适配器设计
带你深入探索okio组件的奥秘 提升IO效率
操作条件对固体氧化物燃料电池阳极反应转变的影响
电气线路起火的原因及预防
幼儿园用光有什么讲究?中国(南昌)教育照明与智慧黑板高峰论坛专家有话说
数字货币下的货币竞争有了什么改变
Linux 开发者 Christopher Helwig 决定放弃上诉 VMware 源码侵权案
开关柜上电器装置的要求盘点
谷歌预计今年再裁员,力推AI等领域投资
区块链技术的价值主要体现在哪里
2019年10大让锂离子电池性能飙升的新型材料
关于智能照明控制系统在学校中的设计与应用
呼吸阀气密性测试仪的适用范围是什么
TWS蓝牙耳机的未来
三大快充技术实测
NE555定时器电路原理分析