鸿蒙新特性实战:@ohos.util 打造数据编解码实验室(Base64 / UTF-8 / LRUCache)
前言
做应用开发,总有些"看不见但离不开"的基础工具:把一段文本编码成 Base64 塞进请求体,把字符串转成字节数组做协议封包,或者用一个缓存把频繁访问的数据兜住、减少重复计算。这些活儿在其他语言里往往依赖第三方库,而在 HarmonyOS NEXT 里,@ohos.util 已经把它们打包成一套标准能力,开箱即用,零权限、纯本地、无网络依赖。
@ohos.util 是 ArkTS 标准库中最实用却最容易被忽视的模块之一。它提供了 Base64 编解码(Base64Helper)、文本与字节互转(TextEncoder / TextDecoder)、LRU 缓存(LRUCache)、有理数(RationalNumber)、区间(ScopeHelper)、类型检测(types)等一整套工具类。
本文将聚焦其中最常用的三块——Base64 编解码、UTF-8 字节编码、LRU 缓存——用一个可交互的"数据编解码实验室"页面把它们串起来:输入文本实时编码成 Base64 并往返校验、查看中英文的 UTF-8 字节展开、用可视化的方式演示 LRU 缓存的命中与淘汰。全文含完整可运行代码,适合中级开发者系统掌握这套基础工具。
一、@ohos.util 概览
引入方式(Kit 化写法):
import { util } from '@kit.ArkTS';
util 命名空间下有几个高频类:
| 类 | 作用 |
|---|---|
util.Base64Helper |
Base64 编码 / 解码,支持同步与异步 |
util.TextEncoder |
字符串 → UTF-8 / UTF-16 字节数组 |
util.TextDecoder |
字节数组 → 字符串 |
util.LRUCache<K,V> |
最近最少使用缓存,自动淘汰 |
util.RationalNumber |
有理数(分数)运算 |
util.ScopeHelper |
区间范围判断 |
util.types |
运行时类型检测(isArrayBuffer 等) |
本文实验室页面会重点用到前三个 + LRUCache。
二、Base64 编解码
2.1 为什么需要 Base64
Base64 把任意二进制数据用 64 个可打印字符(A-Z、a-z、0-9、+、/)表示出来,主要用途是:在只能传输文本的通道里携带二进制数据。比如把图片嵌进 JSON、在 URL 里传递二进制 token、在邮件协议里附带附件。它不是加密,只是一种"可打印化"的编码。
需要注意:Base64 处理的是字节,而不是字符。所以对一段文本做 Base64,必须先把文本按某种字符编码(通常 UTF-8)转成字节数组,再对字节数组做 Base64。这一步在很多初学者那里容易搞混。
2.2 核心 API
const base64 = new util.Base64Helper();
// 编码:Uint8Array → Base64 字符串
const b64: string = base64.encodeToStringSync(bytes);
// 解码:Base64 字符串 → Uint8Array
const back: Uint8Array = base64.decodeSync(b64);
Base64Helper 还有异步版本 encodeToString(src)、decode(src)(返回 Promise),以及带编码选项的重载(如 util.Type.BASIC / util.Type.MIME / util.Type.URL_SAFE)。本文用同步版即可。
2.3 完整编码流程
在实验室页面里,一次完整的 Base64 往返如下:
// 文本 → UTF-8 字节 → Base64 字符串
private encodeBase64(): void {
const encoder = new util.TextEncoder();
const bytes: Uint8Array = encoder.encodeInto(this.inputText);
this.base64Result = this.base64.encodeToStringSync(bytes);
// 再解码回文本,验证往返一致
const back: Uint8Array = this.base64.decodeSync(this.base64Result);
const decoder = util.TextDecoder.create('utf-8');
this.decodedResult = decoder.decodeToString(back);
}
这段代码把整个链路走了一遍:
TextEncoder.encodeInto()把字符串编成 UTF-8 字节数组;Base64Helper.encodeToStringSync()把字节数组转成 Base64 字符串;- 解码时反向:
decodeSync()得回字节数组,TextDecoder.decodeToString()还原成文本。
运行后,输入"HarmonyOS 鸿蒙",你会得到一串 Base64,再解码又能一字不差地还原回来——这就是编解码"可逆"的直观验证。
易错点:直接对字符串做 Base64 是不行的,
encodeToStringSync只接受Uint8Array。必须先经TextEncoder转字节。反过来解码得到的也是字节,要再用TextDecoder才能变回可读文本。
三、UTF-8 字节编码
3.1 TextEncoder / TextDecoder
TextEncoder 负责把字符串编码成字节,TextDecoder 负责反向解码。它们是理解字符编码的最佳教具。
// 编码
const encoder = new util.TextEncoder(); // 默认 UTF-8
const bytes: Uint8Array = encoder.encodeInto('鸿蒙A1');
// 解码
const decoder = util.TextDecoder.create('utf-8'); // 指定编码
const text: string = decoder.decodeToString(bytes);
注意早期 API 里 TextEncoder 的 encode() 方法已被标记 deprecated,推荐用 encodeInto();TextDecoder 用静态工厂 create() 构造,配合 decodeToString() 得到字符串。
3.2 观察多字节编码
实验室页面里,我们把编码后的字节数组同时用十进制和十六进制展示出来:
// 文本 → UTF-8 字节数组的十进制/十六进制展示
private encodeUtf8(): void {
const encoder = new util.TextEncoder();
const bytes: Uint8Array = encoder.encodeInto(this.utf8Input);
this.byteCount = bytes.length;
let dec: string = '';
let hex: string = '';
for (let i = 0; i < bytes.length; i++) {
dec += bytes[i].toString();
hex += bytes[i].toString(16).padStart(2, '0').toUpperCase();
if (i < bytes.length - 1) {
dec += ' ';
hex += ' ';
}
}
this.decList = dec;
this.hexList = hex;
}
输入"鸿蒙A1"跑一遍,你会观察到一个重要现象:
- 字母
A、数字1各占 1 个字节(它们在 ASCII 范围内); - 汉字"鸿""蒙"各占 3 个字节(UTF-8 对中日韩汉字通常用 3 字节表示)。
所以"鸿蒙A1"这 4 个字符,编码后是 8 个字节(3 + 3 + 1 + 1)。这直观解释了为什么"字符长度"不等于"字节长度"——在做数据库字段长度限制、网络封包、字符串截断时,这个区别至关重要。很多"中文乱码""字符串被截断成半个汉字"的 Bug,根源都在这里。


四、LRUCache 缓存
4.1 什么是 LRU
LRU(Least Recently Used,最近最少使用)是一种经典的缓存淘汰策略:当缓存满了、需要腾地方时,优先淘汰"最久没被访问过"的那一项。它的假设很朴素——最近用过的东西,接下来大概率还会用;很久没碰的,大概率也不会再碰。
util.LRUCache 把这套策略封装成了一个泛型容器,你只管 put(放入)、get(取出),容量控制和淘汰它全帮你搞定,还内置了命中率统计。
4.2 核心 API
// 创建容量为 3 的缓存
const lru = new util.LRUCache<string, string>(3);
lru.put('A', '苹果'); // 放入
const v = lru.get('A'); // 取出(会把 A 标记为"最近使用")
lru.contains('A'); // 是否包含某 key(不影响使用顺序)
lru.remove('A'); // 移除
lru.clear(); // 清空
lru.updateCapacity(5); // 动态调整容量
// 统计
lru.length; // 当前元素个数
lru.getCapacity(); // 容量上限
lru.getMatchCount(); // 累计命中次数
lru.getMissCount(); // 累计未命中次数
lru.getPutCount(); // 累计放入次数
其中命中/未命中统计特别适合用来教学:get 一个存在的 key,getMatchCount 加一;get 一个不存在(或已被淘汰)的 key,getMissCount 加一。这让"缓存到底有没有起作用"变得可量化。
4.3 实验室里的封装
在页面里,我们把 LRUCache 实例作为普通成员持有,并在每次操作后把统计数据同步到 @State,以触发界面刷新:
private base64: util.Base64Helper = new util.Base64Helper();
private lru: util.LRUCache<string, string> = new util.LRUCache(3);
@State matchCount: number = 0;
@State missCount: number = 0;
@State cacheSize: number = 0;
@State refreshFlag: number = 0;
private refreshStats(): void {
this.matchCount = this.lru.getMatchCount();
this.missCount = this.lru.getMissCount();
this.cacheSize = this.lru.length;
this.refreshFlag++;
}
private putCache(item: DataItem): void {
this.lru.put(item.key, item.value);
this.addLog('put(' + item.key + ') 若已满则淘汰最久未用项');
this.refreshStats();
}
private getCache(item: DataItem): void {
const v: string | undefined = this.lru.get(item.key);
if (v === undefined) {
this.addLog('get(' + item.key + ') → 未命中 MISS');
} else {
this.addLog('get(' + item.key + ') → 命中 「' + v + '」');
}
this.refreshStats();
}
这里有一个 ArkTS 状态管理的实用技巧。LRUCache 是个普通对象,它内部的变化(比如淘汰)不会被框架自动侦测。为了让"某个 key 是否还在缓存"能实时反映到 UI,我们引入一个自增的 refreshFlag,并在判断函数里"读一下"它:
// 借助 refreshFlag 触发重算,通过 contains 判断某 key 是否仍在缓存
private inCache(key: string): boolean {
const flag: number = this.refreshFlag;
return flag >= 0 && this.lru.contains(key);
}
由于 inCache 读取了 @State refreshFlag,每当 refreshStats() 让 refreshFlag++,所有调用 inCache 的 UI 都会被重新求值,从而实时显示缓存的最新状态。这是"用一个哨兵状态变量驱动非响应式数据刷新"的常见套路。
4.4 可视化淘汰过程
页面用一排色块展示 5 个样本 key(A/B/C/D/E)当前是否在缓存里:在缓存的显示为高亮的青色,已淘汰的显示为灰色。
Row() {
ForEach(this.samples, (item: DataItem) => {
Column() {
Text(item.key)
.fontColor(this.inCache(item.key) ? '#FFFFFF' : '#94A3B8')
.fontWeight(FontWeight.Bold)
Text(item.value)
.fontColor(this.inCache(item.key) ? '#CCFBF1' : '#CBD5E1')
}
.layoutWeight(1)
.backgroundColor(this.inCache(item.key) ? '#0F766E' : '#E2E8F0')
.borderRadius(8)
})
}
配合下方的"放入"和"查询"两排按钮,你可以亲手复现 LRU 的经典行为。以容量 3 为例:
- 依次点"放 A"“放 B”“放 C” → 缓存装满 [A, B, C];
- 点"放 D" → 缓存已满,淘汰最久未用的 A,变成 [B, C, D],色块 A 变灰;
- 点"查 A" → 未命中(missCount +1),因为 A 已被淘汰;
- 点"查 B" → 命中(matchCount +1),同时 B 被标记为最近使用;
- 此时再"放 E" → 淘汰的是 C(而不是 B,因为刚查过 B),变成 [B, D, E]。
第 5 步是理解 LRU 的关键:get 操作也会刷新"最近使用"的时间戳。正因为第 4 步查询了 B,B 才在第 5 步的淘汰中幸免。你还可以点"容量 +""容量 -"动态调整容量,观察扩容后不再淘汰、缩容后立即淘汰的效果。
五、页面整体结构
整个实验室用一个三段式的 Tab 结构组织:
@Entry
@Component
struct EncodeLabPage {
@State activeTab: number = 0;
build() {
Column() {
// 标题栏
Row() { /* 返回按钮 + 标题 + @ohos.util 角标 */ }
.backgroundColor('#0F766E')
// Tab 切换
Row() {
this.tabBtn('Base64', 0)
this.tabBtn('UTF-8 字节', 1)
this.tabBtn('LRU 缓存', 2)
}
// 内容区
Scroll() {
Column() {
if (this.activeTab === 0) {
this.base64Tab()
} else if (this.activeTab === 1) {
this.utf8Tab()
} else {
this.lruTab()
}
}
}
.layoutWeight(1)
.scrollBar(BarState.Off)
}
}
}
三个 Tab 各自封装成 @Builder 方法(base64Tab / utf8Tab / lruTab),互不干扰。结果卡片、统计盒子、Tab 按钮都抽成可复用的 @Builder,让主体逻辑保持清爽:
@Builder
resultCard(title: string, content: string, color: string) {
Column() {
Text(title).fontColor(color).fontWeight(FontWeight.Bold)
Text(content.length > 0 ? content : '(点击上方按钮生成结果)')
.fontColor(content.length > 0 ? '#1F2937' : '#94A3B8')
.fontFamily('monospace')
}
.backgroundColor('#FFFFFF')
.borderRadius(10)
}
用 monospace 等宽字体展示编码结果,让 Base64 串和字节序列对齐更整齐,视觉上更接近"数据"的感觉。
六、几个实战要点与坑
6.1 字符 ≠ 字节
前面反复出现的这个主题,值得单独强调。在做以下场景时务必按字节而非字符处理:
- 网络协议封包:长度字段填的是字节数;
- 数据库 varchar 限制:UTF-8 下一个汉字算 3 字节;
- 字符串安全截断:按字节截断可能切碎一个多字节字符,导致乱码。
TextEncoder.encodeInto() + Uint8Array.length 是获取真实字节长度的可靠手段。
6.2 Base64 处理的是字节
再次提醒:Base64 不能直接吃字符串。文本 → Base64 必须经过 TextEncoder;Base64 → 文本必须经过 TextDecoder。跳过这一步是新手最常见的错误。
6.3 LRUCache 的非响应式陷阱
LRUCache 是普通对象,增删不会自动触发 UI 刷新。要么像本文这样用一个 refreshFlag 哨兵状态驱动重算,要么维护一个 @State 镜像数组。切忌以为改了 LRUCache 界面就会自动更新。
6.4 UI 块内不能写普通语句
ArkTS 声明式语法要求 UI 描述块里只能有组件调用,不能写 let x = ... 这样的赋值语句。所有中间计算都要封装进方法,在 UI 中以方法调用(表达式)的形式使用,例如 this.inCache(item.key)。
七、@ohos.util 的更多能力
除了本文覆盖的三块,util 还有不少值得一试的工具:
- RationalNumber:有理数运算,
util.RationalNumber.parseRationalNumber(1, 3)精确表示 1/3,避免浮点误差,做比例、分数场景很有用; - ScopeHelper:区间判断,判断某值是否落在
[lower, upper]范围内; - types:运行时类型检测,
util.types.isArrayBuffer(x)、isUint8Array(x)等,处理未知数据结构时用得上; - generateRandomUUID():直接生成符合 RFC 4122 的 UUID 字符串,做唯一 id 非常方便;
- format / printf 风格格式化:
util.format('%s = %d', 'x', 42)。
这些工具组合起来,基本覆盖了日常开发对"数据处理基础设施"的需求,而且全部内置、零依赖。
八、小结
本文以"数据编解码实验室"为载体,系统讲解了 HarmonyOS NEXT 标准库 @ohos.util 中三块最实用的能力:
- Base64 编解码:
Base64Helper.encodeToStringSync/decodeSync,配合 TextEncoder/Decoder 完成文本 ↔ Base64 的可逆往返; - UTF-8 字节编码:
TextEncoder.encodeInto/TextDecoder.decodeToString,直观呈现"字符 ≠ 字节"这一核心概念; - LRU 缓存:
LRUCache的 put/get/contains/updateCapacity 与命中率统计,可视化演示了 LRU 的淘汰逻辑与"get 也算最近使用"的关键细节。
同时也覆盖了几个工程实践要点:字符与字节的区别、Base64 只处理字节、LRUCache 的非响应式刷新技巧、以及 ArkTS UI 块的语法限制。这些看似基础,却是构建稳健数据处理逻辑的地基。把 @ohos.util 用熟,你就不必再为编解码和缓存去引入额外依赖了。
更多推荐




所有评论(0)