零基础鸿蒙应用开发第十九节:解锁灵活数据存储新技能Map/Set

零基础鸿蒙应用开发学习计划表

【学习目标】

  1. 掌握Set集合的无重复特性,以及创建、增删、查询、遍历等核心操作
  2. 掌握Map集合的多类型键特性,以及键值对的增删改查、遍历等核心操作
  3. 理解Set/Map与普通数组/对象的核心区别,掌握新手阶段常见场景的选型方法
  4. 能运用Set/Map解决“数组去重”“多类型键存储”等高频问题
  5. 综合运用SetMap实现简单的业务场景开发

【学习重点】

  1. Set核心:自动去重、has()方法O(1)高效查询,适配数组去重场景
  2. Map核心:支持数字/字符串等多类型键、有序存储,适配频繁操作的键值对场景
  3. 遍历方式:for-of、forEach的正确使用(新手友好版)
  4. 场景选型:去重用Set、多类型键/频繁增删用Map、简单临时数据用普通对象
  5. 综合应用:结合SetMap实现基础业务逻辑

一、工程结构

本节我们将创建名为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...offorEach等遍历方式,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(存储学生成绩)实现基础的学生成绩管理功能,包括:

  1. 添加学生成绩(自动去重,避免重复添加同一学生)
  2. 查询指定学生的成绩
  3. 修改学生成绩
  4. 删除学生信息
  5. 统计及格学生(成绩≥60)人数
  6. 展示所有学生成绩

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 练习解析

  1. Set的核心作用:用studentIds存储学生ID,通过has()方法快速判断学生是否已存在,实现添加去重,时间复杂度O(1);
  2. Map的核心作用:用studentScoreMap存储学生完整成绩信息,键为学生ID(数字类型),值为对象,支持高效的增删改查;
  3. 封装思想:将不同功能封装为独立函数,代码结构清晰,符合新手阶段的代码规范;
  4. 遍历应用:通过for...of遍历Map实现成绩统计和列表展示,是新手最易掌握的遍历方式;
  5. 非空判断:对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 语法规范

  1. Set/Map必须显式声明泛型,避免any类型编译器报错
  2. Map.get()结果需判空后再使用,防止undefined错误;
  3. Map值类型优先用接口约束,不用宽泛object

7.2 避坑要点

  1. Set仅支持基础类型去重,对象去重需自定义逻辑;
  2. 避免用对象作Map键(比较引用,易泄漏);
  3. 不可用下标访问Set,修改Map值必须用set()方法。

7.3 选型口诀

  • 去重/存在性校验 → Set
  • 多类型键/高频增删 → Map
  • 简单临时存储 → 普通对象

八、核心总结

  1. 本质:Set/Map是ArkTS内置引用类型集合,专为特定场景设计,操作更高效;
  2. 价值:Set解决去重/高效存在性判断,Map解决多类型键值对存储;
  3. 选型:按场景选——侧重“唯一性”用Set,侧重“键值映射”用Map,简单场景用普通对象。

九、下节预告

在我们前序章节内容中每一节其实都设计到函数的导入导出,我们只是为了方便代码书写,结构清晰。
下节将聚焦ArkTS工程化开发核心——模块导入(import)与导出(export),彻底掌握:

  1. export导出函数/接口/变量的常用方式;
  2. import按需导入、整体导入的语法规则;
  3. 多文件工程中如何通过模块拆分实现代码复用(如本节utils目录的拆分逻辑)。
posted @ 2026-01-21 16:25  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报