关于harmonyos:为你推荐一款高效的IO组件okio

8次阅读

共计 4829 个字符,预计需要花费 13 分钟才能阅读完成。

前不久,三方组件库上新了一批 JS/eTS 组件,其中就包含 okio 组件。okio 是一个可利用于 HarmonyOS 的高效 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. Segment
okio 将数据宰割成一块块的片段寄存在 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 信息,将其增加到输入流缓冲区的双向链表的尾部,省去一次数据复制过程。

(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 的数据写入输入流缓冲区。

拆分 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"]        
        }      
      }    
    ]   
    }
  1. 代码实现执行完下面的配置操作后,就能够进入代码编写阶段了。开发者能够应用 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…

正文完
 0