零基础鸿蒙应用开发第十九节:解锁灵活数据存储新技能Map/Set
【学习目标】
- 掌握
Set集合的无重复特性,以及创建、增删、查询、遍历等核心操作 - 掌握
Map集合的多类型键特性,以及键值对的增删改查、遍历等核心操作 - 理解
Set/Map与普通数组/对象的核心区别,掌握新手阶段常见场景的选型方法 - 能运用
Set/Map解决“数组去重”“多类型键存储”等高频问题 - 综合运用
Set和Map实现简单的业务场景开发
【学习重点】
- Set核心:自动去重、
has()方法O(1)高效查询,适配数组去重场景 - Map核心:支持数字/字符串等多类型键、有序存储,适配频繁操作的键值对场景
- 遍历方式:for-of、forEach的正确使用(新手友好版)
- 场景选型:去重用
Set、多类型键/频繁增删用Map、简单临时数据用普通对象 - 综合应用:结合
Set和Map实现基础业务逻辑
一、工程结构
本节我们将创建名为MapSetDemo的工程,基于 鸿蒙5.0(API12) 开发,使用 DevEco Studio 6.0+ 工具,项目结构目录如下:
MapSetDemo/
├── AppScope # 应用全局配置
├── entry # 主模块目录
│ ├── src
│ │ ├── main
│ │ │ ├── ets # ArkTS核心代码目录
│ │ │ │ ├── entryability # 应用入口能力
│ │ │ │ ├── pages # 页面组件目录
│ │ │ │ │ └── Index.ets # 测试入口页面
│ │ │ │ └── utils # 工具函数目录
│ │ │ │ ├── SetTest.ets // Set相关示例
│ │ │ │ ├── MapTest.ets // Map相关示例
│ │ │ │ ├── PracticeTest.ets // 综合练习示例
│ │ ├── resources # 静态资源
│ │ └── module.json5 # 模块配置
│ ├── build-profile.json5 # 构建配置
│ └── hvigorfile.ts # 构建脚本
└── oh_modules # 第三方依赖
二、Set集合:无重复元素的有序集合
Set是ArkTS内置的引用类型集合,核心特性是“存储唯一值”,遍历顺序与元素插入顺序一致,has()方法可O(1)时间复杂度判断元素是否存在,远超数组includes()(O(n))的效率。
2.1 核心特性
- 唯一性:自动过滤重复元素,添加重复值无效果且不报错;
- 有序性:遍历顺序与元素插入顺序完全一致;
- 类型约束:支持泛型限定存储值的类型(如
Set<string>); - 无索引:无法通过下标访问,需通过遍历或
has()查询元素; - 引用类型:实例存储在堆内存,赋值传递引用而非值拷贝。
2.2 Set基础操作(创建、增删、查询)
// utils/SetTest.ets
export function testSetBasic(): void {
// 1. 创建Set:空Set需显式声明泛型,带初始值的Set自动推断类型
const emptySet = new Set<string>();
const fruitSet = new Set(["apple", "banana", "orange", "apple"]); // 自动去重,最终仅3个元素
console.log("\n【Set基础】初始集合:", Array.from(fruitSet)); // 输出:["apple","banana","orange"]
console.log("【Set基础】元素个数:", fruitSet.size); // 输出:3
// 2. 添加元素:add()方法,链式调用支持
fruitSet.add("apple"); // 重复元素,添加失败
fruitSet.add("grape"); // 新增元素成功
console.log("【Set基础】添加后集合:", Array.from(fruitSet)); // 输出:["apple","banana","orange","grape"]
// 3. 查询元素:has()方法返回布尔值
console.log("【Set基础】是否包含banana:", fruitSet.has("banana")); // 输出:true
console.log("【Set基础】是否包含pear:", fruitSet.has("pear")); // 输出:false
// 4. 删除元素:delete()方法返回操作结果
const isDeleted = fruitSet.delete("orange");
console.log("【Set基础】删除orange是否成功:", isDeleted); // 输出:true
console.log("【Set基础】删除后集合:", Array.from(fruitSet)); // 输出:["apple","banana","grape"]
// 5. 清空集合:clear()方法无返回值
fruitSet.clear();
console.log("【Set基础】清空后元素个数:", fruitSet.size); // 输出:0
}
运行效果
【Set基础】初始集合: ["apple","banana","orange"]
【Set基础】元素个数: 3
【Set基础】添加后集合: ["apple","banana","orange","grape"]
【Set基础】是否包含banana: true
【Set基础】是否包含pear: false
【Set基础】删除orange是否成功: true
【Set基础】删除后集合: ["apple","banana","grape"]
【Set基础】清空后元素个数: 0
2.3 Set核心场景——数组去重
Set是ArkTS中数组去重的最优方案,代码简洁且性能高效:
// utils/SetTest.ets
export function testSetDeduplication(): void {
// 数字数组去重
const duplicateNumArr = [10, 20, 20, 30, 10, 40];
const uniqueNumArr = Array.from(new Set(duplicateNumArr));
console.log("\n【Set去重】原数字数组:", duplicateNumArr); // 输出:[10,20,20,30,10,40]
console.log("【Set去重】去重后数组:", uniqueNumArr); // 输出:[10,20,30,40]
// 字符串数组去重
const duplicateStrArr = ["a", "b", "a", "c", "b"];
const uniqueStrArr = Array.from(new Set(duplicateStrArr))
console.log("\n【Set去重】原字符串数组:", duplicateStrArr); // 输出:["a","b","a","c","b"]
console.log("【Set去重】去重后数组:", uniqueStrArr); // 输出:["a","b","c"]
}
运行效果
【Set去重】原数字数组: [10,20,20,30,10,40]
【Set去重】去重后数组: [10,20,30,40]
【Set去重】原字符串数组: ["a","b","a","c","b"]
【Set去重】去重后数组: ["a","b","c"]
2.4 Set遍历方法
Set支持for...of、forEach等遍历方式,keys()/values()返回值一致(因无键):
// utils/SetTest.ets
export function testSetTraversal(): void {
const numSet = new Set([1, 2, 3, 4]);
// 1. 推荐:直接遍历元素(最简洁)
console.log("\n【Set遍历】直接遍历元素:");
for (const num of numSet) {
console.log(`元素:${num}`);
}
// 2. keys()/values()遍历(结果一致)
console.log("\n【Set遍历】values()遍历:");
for (const value of numSet.values()) {
console.log(`值:${value}`);
}
// 3. entries()遍历
console.log("\n【Set遍历】entries()遍历:");
for (const entry of numSet.entries()) { // 修正拼写:entrie → entry
console.log(`键:${entry[0]},值:${entry[1]}`);
}
}
运行效果
【Set遍历】直接遍历元素:
元素:1
元素:2
元素:3
元素:4
【Set遍历】values()遍历:
值:1
值:2
值:3
值:4
【Set遍历】entries()遍历:
键:1,值:1
键:2,值:2
键:3,值:3
键:4,值:4
三、Map集合:多类型键的有序键值对集合
Map是ArkTS内置的引用类型集合,解决普通对象“仅支持字符串/数字作为键”的限制,支持数字、字符串、布尔等多类型键,且键值对有序、增删改查效率为O(1)。
3.1 核心特性
- 多类型键:支持数字、字符串、布尔等基础类型作为键(普通对象仅支持字符串/数字);
- 有序性:遍历顺序与键值对插入顺序完全一致;
- 便捷计数:通过
size属性直接获取键值对数量(无需手动遍历); - 高效操作:
set()/get()/delete()/has()均为O(1)时间复杂度; - 引用类型:实例存储在堆内存,赋值传递引用。
3.2 Map核心操作(增删改查)
// utils/MapTest.ets
/**
* ArkTS中Map集合使用示例
*/
export function testMapUsage(): void {
// 1. 空Map:显式指定键类型(string | number)、值类型(string)
const emptyMap = new Map<string | number, string>();
console.log("【Map基础】空Map初始化:", emptyMap); // 统一日志前缀
// 2. 带初始值的Map:显式指定类型(禁止自动推断)
const initMap = new Map<string | number, string>([
["name", "张三"], // 键:string,值:string
[18, "年龄"] // 键:number,值:string
]);
console.log("【Map基础】初始化后Map:", initMap); // 输出:Map(2) {'name' => '张三', 18 => '年龄'}
// 3. 核心方法(带执行结果打印)
initMap.set("gender", "男"); // 添加新键值对(键不存在)
console.log("【Map基础】set添加后:", initMap); // 输出:Map(3) {'name' => '张三', 18 => '年龄', 'gender' => '男'}
initMap.set("name", "张三丰"); // 修改已有键值对(键存在)
console.log("【Map基础】set修改后:", initMap); // 输出:Map(3) {'name' => '张三丰', 18 => '年龄', 'gender' => '男'}
// get方法:非空判断(避免undefined风险)
const nameValue = initMap.get("name");
console.log("【Map基础】get获取name:", nameValue ?? "键不存在"); // 输出:张三丰
const unExistValue = initMap.get("address");
console.log("【Map基础】get获取不存在的键:", unExistValue ?? "键不存在"); // 输出:键不存在
// delete方法:返回布尔值(是否删除成功)
const deleteResult = initMap.delete(18);
console.log("【Map基础】delete删除18:", deleteResult, ",删除后Map:", initMap); // 输出:true,Map(2) {'name' => '张三丰', 'gender' => '男'}
// has方法:判断键是否存在
const hasGender = initMap.has("gender");
console.log("【Map基础】has判断gender是否存在:", hasGender); // 输出:true
// clear方法:清空所有键值对
initMap.clear();
console.log("【Map基础】clear清空后:", initMap); // 输出:Map(0) {}
}
运行效果
【Map基础】空Map初始化: Map(0) {}
【Map基础】初始化后Map: Map(2) {'name' => '张三', 18 => '年龄'}
【Map基础】set添加后: Map(3) {'name' => '张三', 18 => '年龄', 'gender' => '男'}
【Map基础】set修改后: Map(3) {'name' => '张三丰', 18 => '年龄', 'gender' => '男'}
【Map基础】get获取name: 张三丰
【Map基础】get获取不存在的键: 键不存在
【Map基础】delete删除18: true ,删除后Map: Map(2) {'name' => '张三丰', 'gender' => '男'}
【Map基础】has判断gender是否存在: true
【Map基础】clear清空后: Map(0) {}
3.3 Map遍历方法(原3.4,删除用户信息管理后编号修正)
Map支持遍历键、值、键值对,推荐使用for...of:
// utils/MapTest.ets
/**
* ArkTS中Map集合的遍历方式
*/
export function testMapTraversal(): void {
// 1. 初始化Map:显式标注键值类型(键:string,值:string | number)
const bookMap = new Map<string, string | number>([
["title", "ArkTS实战"],
["author", "鸿蒙开发者"],
["price", 99]
]);
// 2. 遍历键值对
console.log("\n【Map遍历】方式1 - 遍历键值对(entries()):");
for (const entry of bookMap.entries()) {
console.log(`键:${entry[0]},值:${entry[1]},值类型:${typeof entry[1]}`);
}
// 3. 遍历键(keys())
console.log("\n【Map遍历】方式2 - 仅遍历键:");
for (const key of bookMap.keys()) {
console.log(`键:${key}`);
}
// 4. 遍历值(values())
console.log("\n【Map遍历】方式3 - 仅遍历值:");
for (const value of bookMap.values()) {
console.log(`值:${value}`);
}
// 5.forEach遍历(ArkTS/TS常用遍历方式)
console.log("\n【Map遍历】方式4 - forEach遍历(更简洁):");
bookMap.forEach((value, key) => {
console.log(`键:${key},值:${value}`);
});
}
运行效果
【Map遍历】方式1 - 遍历键值对(entries()):
键:title,值:ArkTS实战,值类型:string
键:author,值:鸿蒙开发者,值类型:string
键:price,值:99,值类型:number
【Map遍历】方式2 - 仅遍历键:
键:title
键:author
键:price
【Map遍历】方式3 - 仅遍历值:
值:ArkTS实战
值:鸿蒙开发者
值:99
【Map遍历】方式4 - forEach遍历(更简洁):
键:title,值:ArkTS实战
键:author,值:鸿蒙开发者
键:price,值:99
四、综合练习:学生成绩管理系统
4.1 练习目标
综合运用Set(学生ID去重)和Map(存储学生成绩)实现基础的学生成绩管理功能,包括:
- 添加学生成绩(自动去重,避免重复添加同一学生)
- 查询指定学生的成绩
- 修改学生成绩
- 删除学生信息
- 统计及格学生(成绩≥60)人数
- 展示所有学生成绩
4.2 完整代码实现
// utils/PracticeTest.ets
/**
* 综合练习:学生成绩管理系统
* 核心:Set(学生ID去重) + Map(成绩存储)
*/
// 1. 定义学生类型接口(强类型约束)
interface Student {
id: number; // 学生ID(唯一)
name: string; // 学生姓名
score: number; // 成绩(0-100)
}
// 2. 初始化核心集合
// Set:存储学生ID,用于去重校验(O(1)效率)
const studentIds = new Set<number>();
// Map:存储学生成绩,键=学生ID,值=Student对象
const studentScoreMap = new Map<number, Student>();
// 3. 功能函数实现
/**
* 添加学生成绩(自动去重)
* @param student 学生信息
*/
function addStudent(student: Student): void {
// 校验:ID已存在则添加失败
if (studentIds.has(student.id)) {
console.log(`\n【综合练习】添加失败:学生ID=${student.id}已存在`);
return;
}
// 新增:添加ID到Set,添加信息到Map
studentIds.add(student.id);
studentScoreMap.set(student.id, student);
console.log(`\n【综合练习】添加成功:${student.name}(ID=${student.id})`);
}
/**
* 展示所有学生成绩
*/
function showAllScores(): void {
console.log("\n【综合练习】所有学生成绩列表:");
// 完全适配ArkTS:无解构,通过entries() + 下标访问
for (const entry of studentScoreMap.entries()) {
const id = entry[0];
const student = entry[1];
const status = student.score >= 60 ? "及格" : "不及格";
console.log(`ID:${id} → 姓名:${student.name},成绩:${student.score}(${status})`);
}
}
/**
* 查询指定学生的成绩
* @param id 学生ID
*/
function queryStudent(id: number): void {
const student = studentScoreMap.get(id);
if (student) {
console.log(`\n【综合练习】查询结果:ID=${id} → 姓名:${student.name},成绩:${student.score}`);
} else {
console.log(`\n【综合练习】查询失败:未找到ID=${id}的学生`);
}
}
/**
* 修改学生成绩
* @param id 学生ID
* @param newScore 新成绩
*/
function updateScore(id: number, newScore: number): void {
// 校验:学生是否存在
if (!studentIds.has(id)) {
console.log(`\n【综合练习】修改失败:未找到ID=${id}的学生`);
return;
}
const student = studentScoreMap.get(id); // 修正多余空格
if (student) {
student.score = newScore;
studentScoreMap.set(id, student);
console.log(`\n【综合练习】修改成功:ID=${id}的成绩已更新为${newScore}`);
}
}
/**
* 统计及格学生(≥60分)人数
*/
function countPassedStudents(): void {
let passedCount = 0;
// 遍历Map统计及格人数
for (const student of studentScoreMap.values()) {
if (student.score >= 60) {
passedCount++;
}
}
const totalCount = studentScoreMap.size;
console.log(`\n【综合练习】及格学生人数:${passedCount}(总人数:${totalCount})`);
}
/**
* 删除指定学生信息
* @param id 学生ID
*/
function deleteStudent(id: number): void {
// 校验:学生是否存在
if (!studentIds.has(id)) {
console.log(`\n【综合练习】删除失败:未找到ID=${id}的学生`);
return;
}
// 删除:同步移除Set和Map中的数据
studentIds.delete(id);
studentScoreMap.delete(id);
console.log(`\n【综合练习】删除成功:ID=${id}的学生信息已移除`);
}
// 4. 测试入口函数(导出,便于页面调用)
export function testStudentScoreSystem(): void {
// ========== 测试功能 ==========
// 添加学生
addStudent({ id: 101, name: "张三", score: 85 });
addStudent({ id: 102, name: "李四", score: 58 });
addStudent({ id: 103, name: "王五", score: 92 });
addStudent({ id: 101, name: "张三", score: 85 }); // 重复添加,预期失败
// 展示所有成绩
showAllScores();
// 查询学生成绩
queryStudent(102);
queryStudent(105); // 不存在的ID
// 修改成绩
updateScore(102, 65);
// 统计及格人数
countPassedStudents();
// 删除学生
deleteStudent(103);
deleteStudent(104); // 不存在的ID
// 最终成绩展示
showAllScores();
}
4.3 运行效果
【综合练习】添加成功:张三(ID=101)
【综合练习】添加成功:李四(ID=102)
【综合练习】添加成功:王五(ID=103)
【综合练习】添加失败:学生ID=101已存在
【综合练习】所有学生成绩列表:
ID:101 → 姓名:张三,成绩:85(及格)
ID:102 → 姓名:李四,成绩:58(不及格)
ID:103 → 姓名:王五,成绩:92(及格)
【综合练习】查询结果:ID=102 → 姓名:李四,成绩:58
【综合练习】查询失败:未找到ID=105的学生
【综合练习】修改成功:ID=102的成绩已更新为65
【综合练习】及格学生人数:3(总人数:3)
【综合练习】删除成功:ID=103的学生信息已移除
【综合练习】删除失败:未找到ID=104的学生
【综合练习】所有学生成绩列表:
ID:101 → 姓名:张三,成绩:85(及格)
ID:102 → 姓名:李四,成绩:65(及格)
4.4 练习解析
- Set的核心作用:用
studentIds存储学生ID,通过has()方法快速判断学生是否已存在,实现添加去重,时间复杂度O(1); - Map的核心作用:用
studentScoreMap存储学生完整成绩信息,键为学生ID(数字类型),值为对象,支持高效的增删改查; - 封装思想:将不同功能封装为独立函数,代码结构清晰,符合新手阶段的代码规范;
- 遍历应用:通过
for...of遍历Map实现成绩统计和列表展示,是新手最易掌握的遍历方式; - 非空判断:对
Map.get()的结果做判空处理,避免undefined错误,是ArkTS开发的基础规范。
五、代码调用入口(pages/Index.ets)
import { testSetBasic, testSetDeduplication, testSetTraversal } from '../utils/SetTest';
import { testMapUsage, testMapTraversal } from '../utils/MapTest';
import { testStudentScoreSystem } from '../utils/PracticeTest';
@Entry
@Component
struct Index {
aboutToAppear() {
// 测试Set基础操作
testSetBasic()
// 测试Set数组去重
testSetDeduplication()
// 测试Set遍历
testSetTraversal()
// 测试Map基础操作
testMapUsage()
// 测试Map基础操作
testMapUsage()
// 测试Map遍历
testMapTraversal()
//运行学生成绩管理系统
testStudentScoreSystem()
}
build() {
Column({ space: 15 }) {
Text("解锁灵活数据存储新技能-集合")
.fontSize(20)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.padding(20);
}
}
六、核心区别与场景选型表
| 类型/工具 | 类型本质 | 核心能力 | 性能特点 | 最佳场景 |
|---|---|---|---|---|
Set |
内置引用类型集合 | 存储唯一值、O(1)存在性判断 | 遍历/查询高效 | 数组去重、元素存在性校验 |
Map |
内置引用类型集合 | 多类型键、有序键值对操作 | 增删查高效 | 用户ID映射、频繁操作的键值对 |
| 普通对象 | 引用类型 | 无约束的键值对存储 | 同普通对象 | 临时数据、无类型约束的简单键值对 |
七、开发规范与避坑指南
7.1 语法规范
- 空
Set/Map必须显式声明泛型,避免any类型编译器报错 Map.get()结果需判空后再使用,防止undefined错误;Map值类型优先用接口约束,不用宽泛object。
7.2 避坑要点
- Set仅支持基础类型去重,对象去重需自定义逻辑;
- 避免用对象作Map键(比较引用,易泄漏);
- 不可用下标访问Set,修改Map值必须用
set()方法。
7.3 选型口诀
- 去重/存在性校验 → Set
- 多类型键/高频增删 → Map
- 简单临时存储 → 普通对象
八、核心总结
- 本质:
Set/Map是ArkTS内置引用类型集合,专为特定场景设计,操作更高效; - 价值:Set解决去重/高效存在性判断,Map解决多类型键值对存储;
- 选型:按场景选——侧重“唯一性”用Set,侧重“键值映射”用Map,简单场景用普通对象。
九、下节预告
在我们前序章节内容中每一节其实都设计到函数的导入导出,我们只是为了方便代码书写,结构清晰。
下节将聚焦ArkTS工程化开发核心——模块导入(import)与导出(export),彻底掌握:
export导出函数/接口/变量的常用方式;import按需导入、整体导入的语法规则;- 多文件工程中如何通过模块拆分实现代码复用(如本节utils目录的拆分逻辑)。
浙公网安备 33010602011771号