在纯血鸿蒙(HarmonyOS Next)的@ohos.data.preferences(首选项)中,HashMap不能直接存储,而Record<string, T>(或普通对象{})可以通过 JSON 序列化间接支持,其根本原因不在于“鸿蒙是否支持”,而在于JavaScript/ArkTS 的序列化机制与Preferences的底层设计限制。
下面我们从本质、序列化行为、类型系统、运行时表现四个维度对比HashMap与Record,并解释为何只有后者能用于Preferences。
一、核心结论(先说答案)
| 特性 | HashMap(即Map) | Record<string, T>(普通对象) |
|---|---|---|
能否被JSON.stringify()正确序列化? | ❌ 否(结果为{}) | ✅ 是(保留键值对) |
是否原生支持Preferences存储? | ❌ 不支持 | ⚠️ 间接支持(需转 JSON 字符串) |
| 底层数据结构 | 哈希表(非原型链属性) | 普通对象(可枚举 own properties) |
鸿蒙Preferences要求 | 必须是string/number/boolean | 可通过字符串中转 |
✅关键点:
Preferences本身只存字符串,能否用取决于你能否把它变成有效的 JSON 字符串。
而Map→ JSON 会丢失数据,Record→ JSON 则完整保留。
二、深度对比:MapvsRecord(普通对象)
1.数据结构本质不同
| 类型 | 描述 |
|---|---|
Map<K, V> | ES6 引入的专用哈希表结构,键可以是任意类型(包括对象、函数),内部使用哈希算法存储,不依赖对象的原型链或属性 |
Record<string, T> | TypeScript 对普通 JavaScript 对象{}的类型约束,本质仍是Object,键必须是string/symbol,值通过对象属性存储 |
// Map const map = new Map(); map.set('name', '鸿蒙'); map.set(42, true); // 键可以是 number! // Record (普通对象) const record: Record<string, string> = { name: '鸿蒙', version: 'Next' };2.JSON 序列化行为(决定能否用于 Preferences)
这是最核心的区别!
✅Record/ 普通对象 → JSON ✅
const obj = { a: 1, b: 'hello' }; JSON.stringify(obj); // '{"a":1,"b":"hello"}' → 完美保留❌Map→ JSON ❌
const map = new Map([['a', 1], ['b', 'hello']]); JSON.stringify(map); // '{}' → **空对象!数据全部丢失**📌为什么?
JSON.stringify()只序列化对象的可枚举自有属性(own enumerable properties)。
而Map的键值对不是对象属性,而是存储在内部槽(internal slot)中,对 JSON 不可见。
3.鸿蒙Preferences的存储机制
putSync(key: string, value: ValueType): void; type ValueType = number | string | boolean | Array<number> | Array<string> | Array<boolean> | Uint8Array | object | bigint;Preferences.putSync(key, value)只接受string | number | boolean | Array<> | Unint8Array | object | bigint- 如果你想存复杂数据,必须自己转成字符串(通常是 JSON)
- 因此:
Record→JSON.stringify()→string→ ✅ 可存Map→JSON.stringify()→"{}"→ ❌ 存了也等于没存
4.能否手动让 Map 支持?可以,但需转换
如果你坚持用Map,必须先转为普通对象:
// Map → Object(仅当 key 为 string 时可行) function mapToObject<K extends string, V>(map: Map<K, V>): Record<K, V> { const obj = {} as Record<K, V>; for (const [key, value] of map) { obj[key] = value; } return obj; } // 存储 const myMap = new Map<string, string>([['theme', 'dark']]); const jsonStr = JSON.stringify(mapToObject(myMap)); // '{"theme":"dark"}' prefs.putSync('config', jsonStr);⚠️ 限制:Map 的 key 必须是 string,否则无法转为合法 JSON 对象(JSON key 只能是 string)。
三、对比总结表
| 维度 | Map(HashMap) | Record<string, T>(普通对象) |
|---|---|---|
| 键类型 | 任意类型(string/number/object) | 仅string或symbol |
| JSON.stringify() 结果 | {}(数据丢失) | 完整保留键值对 |
| 能否直接用于 Preferences | ❌ 否 | ⚠️ 需转 JSON 字符串 |
| 性能(大量数据) | 更优(O(1) 查找) | 较差(依赖原型链) |
| 鸿蒙推荐场景 | 内存中高性能操作 | 配置、状态等需持久化的数据 |
| 类型安全(TS) | 强(泛型) | 强(索引签名) |
四、鸿蒙开发最佳实践建议
| 场景 | 推荐类型 |
|---|---|
| 需要持久化到 Preferences | ✅ 使用Record<string, T>或普通对象{} |
| 仅内存中使用,追求性能 | ✅ 使用Map |
| 键包含非 string 类型 | ✅ 只能用Map,但不能直接持久化 |
| 需要监听变化(响应式) | ✅ 鸿蒙推荐@State+ 对象,而非 Map |
💡记住:
Preferences是“字符串仓库”,不是“对象数据库”。
所有复杂数据都必须能无损转为 JSON 字符串。
五、总结
HashMap(Map)不支持 Preferences,不是鸿蒙的限制,而是 JavaScript 语言本身的序列化规则决定的。Record能用,是因为它本质是普通对象,而普通对象能被 JSON 正确序列化。
因此,在鸿蒙开发中:
- 要持久化?→ 用
Record或{} - 要高性能内存操作?→ 用
Map,但别想直接存 Preferences
理解这一点,就能避免“为什么我存了 Map 却读出来是空”的经典坑!