hash(map,object)结构

数组和哈希表(前端常用 Object/Map 实现)的核心区别,我会从本质特征核心操作适用场景三个维度,用更通俗的方式帮你梳理,避免重复且突出关键差异。

一、核心区别(本质 + 操作)

维度数组哈希表(Object/Map)
索引本质「位置索引」:只能用连续数字(0,1,2...),索引和元素的物理位置强绑定「键值映射」:可用任意类型键(字符串 / 数字 / 对象),通过哈希函数映射到存储位置,键与位置无直接关联
查找逻辑1. 按下标查:O (1)(直接定位)2. 按值查:O (n)(必须遍历)1. 按键查:平均 O (1)(哈希函数直接映射)2. 按值查:O (n)(仍需遍历)
有序性天然有序(按下标顺序排列)1. Object:ES6 前无序,ES6 后按插入顺序2. Map:严格保持插入顺序
唯一性元素可重复(允许多个相同值)键 / 属性名唯一(重复赋值会覆盖),值可重复
插入 / 删除1. 尾部操作:O (1)2. 中间操作:O (n)(需移动后续元素)无 “头尾 / 中间” 概念,增删平均 O (1)(仅需修改映射关系),受哈希冲突影响可能退化到 O (n)
空间特性连续内存,空间利用率高;静态数组长度固定,动态数组扩容需复制离散存储,需额外空间存储哈希结构(如冲突链表),空间利用率略低;长度可动态调整,无需提前分配

一、JavaScript 中 Map 直接初始化

JS 的 Map 构造函数支持接收一个二维数组作为参数,二维数组的每一个子数组都是 [key, value] 的形式,这是官方原生、最推荐的初始化方式,无需额外操作。

基础写法(最常用)
// 定义时直接初始化键值对,key 可以是任意类型(数字、字符串、对象等) const map = new Map([ ['name', '张三'], // 字符串key [100, '满分'], // 数字key [true, '布尔值'] // 布尔值key ]); // 验证:直接获取值,无需set console.log(map.get('name')); // 输出:张三 console.log(map.get(100)); // 输出:满分 
特殊场景:key 是对象 / 数组(JS Map 独有的特性)
const objKey = { id: 1 }; const arrKey = [2]; const map = new Map([ [objKey, '对象作为key'], [arrKey, '数组作为key'] ]); console.log(map.get(objKey)); // 输出:对象作为key

map和二维数组的转换

const map = new Map([ ['name', '张三'], // 字符串key [100, '满分'], // 数字key [true, '布尔值'] // 布尔值key ]); console.log(map) console.log(Array.from(map.entries())); 

二、通俗理解 + 前端实战例子

1. 数组:“按位置排好队的储物柜”
  • 通俗比喻:就像超市的储物柜,每个柜子有固定编号(0 号、1 号、2 号),你知道编号就能直接打开,但想找 “放了水杯的柜子”,就得一个个看。

前端例子

// 数组:按下标快速访问,按值查找需遍历 const arr = ["苹果", "香蕉", "橙子"]; console.log(arr[1]); // O(1) 直接取“香蕉” console.log(arr.find(item => item === "橙子")); // O(n) 遍历找值 
2. 哈希表:“按名字贴标签的储物柜”
  • 通俗比喻:储物柜没有固定编号,而是贴标签(比如 “小明的水杯”“小红的伞”),你报标签名,系统能快速找到对应的柜子,不用管柜子的物理位置。

前端例子

// Map:按键快速查找,键可以是任意类型 const map = new Map(); map.set("水果1", "苹果"); map.set(2, "香蕉"); map.set({ id: 3 }, "橙子"); console.log(map.get(2)); // O(1) 直接取“香蕉” console.log(map.get({ id: 3 })); // undefined(对象是引用类型,需用同一个引用) 

三、适用场景(前端高频)

场景需求选数组选哈希表
按顺序存储、频繁按位置访问✅ (如列表渲染、排序)
快速查找 / 匹配(按 “标识” 找)✅ (如缓存、去重、统计频率)
元素可重复、需保持顺序❌(Map 有序但键唯一)
存储键值对、需唯一标识

总结

  1. 数组的核心优势是按位置快速访问 + 天然有序,适合 “按顺序存 / 取” 的场景;
  2. 哈希表的核心优势是按键快速映射,适合 “按标识查 / 存” 的场景;
  3. 两者最核心的区别是索引方式:数组是 “数字位置索引”,哈希表是 “任意键映射索引”。

一、核心场景:数组 vs 哈希表(选型 + 优化)

场景 1:数组去重(高频面试 / 业务场景)

需求:将 [1, 2, 2, 3, 3, 3] 转为无重复数组。

❷ 哈希表优化(高效版)思路:用 Map/Set 记录已出现的元素,将 “查找是否存在” 的操作从 O(n) 降到 O(1)。时间复杂度:O(n)(仅一次遍历)。

function uniqueArrayOptimize(arr) { const map = new Map(); const result = []; for (const item of arr) { // 哈希表查找:O(1) if (!map.has(item)) { map.set(item, true); result.push(item); } } return result; // 极简版:return [...new Set(arr)](底层也是哈希表) } 

❶ 纯数组实现(基础版,效率低)思路:遍历数组,用 indexOf 检查元素是否已存在(本质是遍历)。时间复杂度:O(n2)(嵌套遍历)。

function uniqueArrayBasic(arr) { const result = []; for (let i = 0; i < arr.length; i++) { // indexOf 会遍历 result,嵌套 O(n) if (result.indexOf(arr[i]) === -1) { result.push(arr[i]); } } return result; } 
场景 2:高频词统计(数据处理场景)

需求:统计字符串中每个单词出现的次数,如 "a b a c b a"{a:3, b:2, c:1}

❷ 哈希表实现(最优解)思路:直接用 Object/Map 做键值映射,单词为键、次数为值,查找 / 更新都是 O(1)。时间复杂度:O(n),代码简洁。

function countWordsOptimize(str) { const words = str.split(" "); const countMap = {}; for (const word of words) { // 哈希表更新:O(1) countMap[word] = (countMap[word] || 0) + 1; } return countMap; } 

❶ 纯数组实现(不可行 + 效率低)思路:用两个数组分别存 “单词” 和 “次数”,每次统计需遍历数组找单词。时间复杂度:O(n2),且代码冗余。

function countWordsBasic(str) { const words = str.split(" "); const keys = []; const counts = []; for (const word of words) { const index = keys.indexOf(word); // O(n) 查找 if (index === -1) { keys.push(word); counts.push(1); } else { counts[index]++; } } // 转成对象(额外操作) return keys.reduce((obj, key, i) => ({ ...obj, [key]: counts[i] }), {}); } 
场景 3:有序列表的快速访问(UI 渲染场景)

需求:渲染一个商品列表,支持 “点击第 N 个商品” 快速定位详情。

❷ 哈希表实现(没必要 + 冗余)思路:用哈希表存 {0:商品1, 1:商品2...},虽然访问也是 O(1),但丢失了 “有序” 特性,且无法快速做排序 / 切片。

const goodsMap = new Map([[0, "商品1"], [1, "商品2"], [2, "商品3"], [3, "商品4"]]); // 虽能访问,但无法直接做 goodsList.slice(0,2) 这类有序操作 

❶ 数组实现(最优解)思路:数组天然有序,按下标访问是 O(1),适合按位置操作。

const goodsList = ["商品1", "商品2", "商品3", "商品4"]; // 点击第2个商品(下标1),直接获取:O(1) function getGoodsByIndex(index) { return goodsList[index]; } 
场景 4:缓存数据(性能优化场景)

需求:缓存接口返回的用户信息,避免重复请求,支持 “按用户 ID 快速查找”。

❷ 哈希表实现(最优解)思路:用 Map{1:用户1, 2:用户2},按 ID 查找是 O(1),适合高频键值查询。

const userMap = new Map([[1, { id: 1, name: "小明" }], [2, { id: 2, name: "小红" }]]); function getUserByIdOptimize(id) { return userMap.get(id); // O(1) } 

❶ 数组实现(效率低)思路:用数组存用户列表,按 ID 查找需遍历,O(n)。

const userList = [{ id: 1, name: "小明" }, { id: 2, name: "小红" }]; function getUserById(id) { return userList.find(user => user.id === id); // O(n) } 

二、算法优化核心思路(通用规则)

优化方向用数组优化用哈希表优化
降低时间复杂度利用 “下标 O (1) 访问” 特性,适合有序、按位置操作的场景利用 “键 O (1) 查找” 特性,将遍历查找(O (n))转为哈希映射(O (1))
简化代码逻辑有序遍历、切片、排序等场景,数组 API(forEach/slice/sort)更简洁键值映射、去重、统计等场景,Map/Object 的 API(set/get/has)更直接
空间换时间动态数组扩容(牺牲少量空间,保证访问效率)哈希表的冲突链表(牺牲少量空间,保证查找效率)

总结

  1. 选数组:当需要有序存储按位置访问 / 操作(如列表渲染、排序、切片)时,数组是最优解,核心优势是 “下标 O (1) 访问 + 天然有序”。
  2. 选哈希表:当需要快速键值映射去重 / 统计缓存数据时,哈希表是最优解,核心优势是 “按键 O (1) 查找 + 键唯一性”。
  3. 优化核心:用哈希表将 “数组遍历查找(O (n))” 转为 “键映射(O (1))”,是前端算法优化中最常用的 “空间换时间” 思路。

一、基础概念题(考察核心特性)

1. 简答题:Map 和普通 Object 有哪些核心区别?(高频)

参考答案

特性MapObject
键的类型支持任意类型(字符串 / 数字 / 对象 / 函数 / Symbol)仅支持字符串、Symbol(数字会转为字符串)
有序性严格保持键的插入顺序ES6 前无序,ES6 后仅保留字符串键的插入顺序
长度获取直接通过 size 属性获取需手动遍历(Object.keys(obj).length
迭代方式原生支持迭代(for...of)需先转数组(Object.keys/values/entries)
原型链干扰无(键不会继承原型链属性)可能被原型链属性干扰(如 obj.hasOwnProperty('toString')
性能频繁增删键值对时性能更优增删频繁时性能略差
2. 选择题:以下代码执行结果是什么?
const map = new Map(); const objKey = { id: 1 }; map.set(objKey, "test"); console.log(map.get({ id: 1 })); // ? console.log(map.get(objKey)); // ? 

答案undefinedtest思路Map 的键是引用类型时,判断相等的依据是「引用地址是否相同」,而非值是否相同。{id:1} 是两个不同的对象,引用地址不同,因此第一个 get 返回 undefined

3. 简答题:Mapset 方法返回值是什么?这一特性有什么用?

参考答案

  • set 方法返回 Map 实例本身(支持链式调用);
  • 用途:可以简化代码,比如 map.set('a',1).set('b',2).set('c',3),无需多次写 map.set

二、进阶应用题(考察场景使用)

1. 编程题:用 Map 实现数组去重(要求保留原顺序,支持引用类型元素)

需求:将 [1, 2, 2, {id:1}, {id:1}, {id:2}] 去重,输出 [1, 2, {id:1}, {id:2}]参考答案

function uniqueArray(arr) { const map = new Map(); const result = []; for (const item of arr) { // 引用类型用自身当键,基本类型直接当键 if (!map.has(item)) { map.set(item, true); result.push(item); } } return result; } // 测试 const arr = [1, 2, 2, {id:1}, {id:1}, {id:2}]; console.log(uniqueArray(arr)); // [1,2,{id:1},{id:2}] 

思路:利用 Map 键的唯一性,且支持引用类型作为键,相比 Set 更直观(Set 本质是特殊的 Map),同时保留插入顺序。

2. 编程题:用 Map 优化 “两数之和” 算法(高频算法题)

需求:给定数组 [2,7,11,15] 和目标值 9,找出两个数的下标,使它们的和等于目标值(要求时间复杂度 O (n))。参考答案

function twoSum(nums, target) { const map = new Map(); for (let i = 0; i < nums.length; i++) { const complement = target - nums[i]; // 检查是否已存在互补数 if (map.has(complement)) { return [map.get(complement), i]; } // 存储“数值-下标”映射 map.set(nums[i], i); } return []; } // 测试 console.log(twoSum([2,7,11,15], 9)); // [0,1] 

思路:用 Map 替代嵌套遍历,将 “查找互补数” 的操作从 O (n) 降到 O (1),整体时间复杂度从 O (n²) 优化为 O (n)。

3. 分析题:为什么在频繁增删键值对的场景下,MapObject 更高效?
  • Object 是为静态属性设计的,频繁增删会触发 JS 引擎的 “隐藏类” 重排,导致性能损耗;
  • Map 是为动态键值对场景优化的底层数据结构(哈希表实现),增删操作不会触发隐藏类重排,性能更稳定。

三、手写实现题(考察底层理解)

1. 手写简易版 Map(核心方法:set/get/has/delete/size)

要求:模拟 Map 的核心逻辑,支持任意类型键,保留插入顺序。参考答案

class MyMap { constructor() { // 用两个数组分别存键和值,保证顺序一致 this.keys = []; this.values = []; this.size = 0; } // 核心:判断两个键是否相等(处理引用类型) isEqual(key1, key2) { if (key1 === key2) return true; // 处理 NaN(NaN === NaN 为 false,需特殊判断) if (key1 !== key1 && key2 !== key2) return true; return false; } set(key, value) { // 先查找是否已存在该键 const index = this.keys.findIndex(k => this.isEqual(k, key)); if (index !== -1) { this.values[index] = value; // 存在则更新值 } else { this.keys.push(key); this.values.push(value); this.size++; // 新增则更新size } return this; // 支持链式调用 } get(key) { const index = this.keys.findIndex(k => this.isEqual(k, key)); return index !== -1 ? this.values[index] : undefined; } has(key) { return this.keys.some(k => this.isEqual(k, key)); } delete(key) { const index = this.keys.findIndex(k => this.isEqual(k, key)); if (index !== -1) { this.keys.splice(index, 1); this.values.splice(index, 1); this.size--; return true; } return false; } clear() { this.keys = []; this.values = []; this.size = 0; } } // 测试 const myMap = new MyMap(); myMap.set(NaN, "test").set({id:1}, "obj"); console.log(myMap.get(NaN)); // test console.log(myMap.has({id:1})); // false(引用不同) console.log(myMap.size); // 2 myMap.delete(NaN); console.log(myMap.size); // 1 

核心考点

  • 处理 NaN 的特殊情况(NaN 作为键时,Map 认为是同一个键);
  • 引用类型键的相等判断(引用地址);
  • set 方法返回自身以支持链式调用;
  • size 属性的动态更新。

总结

  1. Map 的核心考点集中在键的类型支持有序性引用类型键的判断规则
  2. 实战场景中,Map 常用来优化 “查找 / 去重 / 统计” 类需求,核心是利用其 O (1) 的键查找效率;
  3. 手写 Map 需重点处理 NaN 和引用类型键的相等判断,以及 size 的动态维护

手动将数组哈希化,核心是遍历数组,构建键值对映射结构(用 Object 或自定义结构),核心逻辑是「以数组元素为键,以需要的信息(存在性、次数、下标)为值」,下面分三种常见场景给出纯手动实现代码(不依赖 Map,更底层)。

一、 手动实现「存在性哈希化」(判断元素是否存在)

需求:将数组转为哈希对象,快速判断元素是否存在,替代 indexOf核心逻辑:元素作为 key,值设为 true,遍历数组逐个赋值。

function arrayToExistHash(arr) { const hash = {}; for (let i = 0; i < arr.length; i++) { const item = arr[i]; // 处理基础类型:数字、字符串、布尔值 // 注意:对象/数组等引用类型会被转为 "[object Object]",不适合用Object存储 hash[item] = true; } return hash; } // 测试 const arr = [1, 2, 3, 2, 1]; const existHash = arrayToExistHash(arr); console.log(existHash[2]); // true console.log(existHash[4]); // undefined 

二、 手动实现「统计型哈希化」(统计元素出现次数)

需求:统计数组中每个元素的出现次数,核心是「元素为键,次数为值」。核心逻辑:遍历数组时,若元素已在哈希中则计数 +1,否则初始化为 1

function arrayToCountHash(arr) { const countHash = {}; for (let i = 0; i < arr.length; i++) { const item = arr[i]; // 三元表达式简化:存在则+1,不存在则设为1 countHash[item] = countHash[item] ? countHash[item] + 1 : 1; } return countHash; } // 测试 const strArr = ["a", "b", "a", "c", "a"]; const countHash = arrayToCountHash(strArr); console.log(countHash); // {a: 3, b: 1, c: 1} 

三、 手动实现「索引型哈希化」(记录元素对应下标)

需求:记录每个元素在数组中的所有下标,核心是「元素为键,下标数组为值」。核心逻辑:遍历数组时,若元素不在哈希中则初始化空数组,再将当前下标推入数组。

function arrayToIndexHash(arr) { const indexHash = {}; for (let i = 0; i < arr.length; i++) { const item = arr[i]; // 初始化:不存在则创建空数组 if (!indexHash[item]) { indexHash[item] = []; } // 存入当前下标 indexHash[item].push(i); } return indexHash; } // 测试 const numArr = [10, 20, 30, 20, 10]; const indexHash = arrayToIndexHash(numArr); console.log(indexHash[10]); // [0, 4] console.log(indexHash[20]); // [1, 3] 

四、 手动哈希化的注意事项(前端开发避坑)

  1. 引用类型无法正确存储
  2. 特殊值的键名转换问题
    • NaNundefinednull 作为键时,会被转为字符串 "NaN""undefined""null"
    • 数字键 1 和字符串键 "1" 会被视为同一个键(因为 hash[1]hash["1"] 指向同一个位置)。
  3. 空间换时间的本质
    • 手动哈希化会额外创建一个对象存储映射关系,内存占用比原数组高,但能将「查找操作」的时间复杂度从 O(n) 降到 O(1)

若要支持引用类型,需手动实现基于引用地址的哈希(模拟 Map 逻辑):

// 用两个数组分别存键和值,基于引用匹配 function arrayToRefHash(arr) { const keys = []; const values = []; for (const item of arr) { if (!keys.includes(item)) { keys.push(item); values.push(true); } } return { keys, values, has: (key) => keys.includes(key) }; } 

Object 做哈希时,对象 / 数组等引用类型会被转为字符串 [object Object],导致不同引用的元素被当成同一个键:

const arr = [{id:1}, {id:2}]; const hash = arrayToExistHash(arr); console.log(hash); // {"[object Object]": true} → 两个对象被覆盖 

Read more

Flutter 三方库 easy_money_formatter 的鸿蒙化适配指南 - 实现具备多种货币符号与千分位自动处理的金额格式化、支持端侧金融应用的动态金额渲染实战

Flutter 三方库 easy_money_formatter 的鸿蒙化适配指南 - 实现具备多种货币符号与千分位自动处理的金额格式化、支持端侧金融应用的动态金额渲染实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 easy_money_formatter 的鸿蒙化适配指南 - 实现具备多种货币符号与千分位自动处理的金额格式化、支持端侧金融应用的动态金额渲染实战 前言 在进行 Flutter for OpenHarmony 的电子钱包、电商支付或个人理财应用开发时,如何优雅、规范地展示金额数值?简单的 toStringAsFixed 无法处理千分位分割以及不同国家/地区的货币符号排列逻辑。easy_money_formatter 是一款轻量级、功能专注的金额处理库。本文将介绍如何在鸿蒙端快速构建符合金融规范的金额展示层。 一、原直观解析 / 概念介绍 1.1 基础原理 该库建立在“格式化掩码(Formatting Mask)”逻辑之上。它接收一段原始的数值(Double 或 String),根据预设的配置(如符号位置、

By Ne0inhk
Flutter for OpenHarmony: Flutter 三方库 envied_generator 给鸿蒙应用的敏感 API Key 穿上“不可破解”的防护服(安全性加固利器)

Flutter for OpenHarmony: Flutter 三方库 envied_generator 给鸿蒙应用的敏感 API Key 穿上“不可破解”的防护服(安全性加固利器)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在进行 OpenHarmony 应用开发时,我们不可避免地要集成各种三方服务(如高德地图 KEY、Firebase Secret、或是鸿蒙分布式服务的授权 Token)。如果你直接将这些字符串写在 Dart 代码里,任何初级黑客都能通过反编译你的 HAP 包,轻松获取这些敏感资产,导致巨大的商业损失。 envied_generator 配合 envied 就是专门解决这一安全痛点的。它不仅能将配置从 .env 文件读取到代码中,更关键的是它支持 Obfuscate(代码混淆)。它将你的 Key 转化为一串复杂的位运算逻辑,让反编译后的结果变得面目全非,为鸿蒙应用的资产安全筑起第一道堤坝。 一、配置加固工作流模型 该库通过代码生成,将明文配置文件转化为混淆后的 Dart 类。 .env (敏感明文) envied_generator

By Ne0inhk
鸿蒙跨平台实战:React Native在OpenHarmony上的AccessibilityInfo辅助功能开关详解

鸿蒙跨平台实战:React Native在OpenHarmony上的AccessibilityInfo辅助功能开关详解

鸿蒙跨平台实战:React Native在OpenHarmony上的AccessibilityInfo辅助功能开关详解 欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 摘要:本文深入探讨React Native中AccessibilityInfo模块在OpenHarmony 6.0.0 (API 20)平台上的实现与应用。作为无障碍功能的核心组件,AccessibilityInfo提供了获取设备辅助功能状态的能力。文章将从技术原理出发,详细分析跨平台适配机制,并通过实战案例展示在OpenHarmony环境下的具体实现。所有代码示例基于React Native 0.72.5和TypeScript 4.8.4编写,已在AtomGitDemos项目中验证通过。读者将掌握如何开发符合无障碍标准的应用,确保在鸿蒙设备上提供一致的用户体验。 1. AccessibilityInfo组件介绍 AccessibilityInfo是React Native提供的核心无障碍功能模块,用于检测和响应设备辅助功能状态的变化。在Ope

By Ne0inhk
Linux 进程间通信之管道基础解析 —— 匿名管道的原理与实现

Linux 进程间通信之管道基础解析 —— 匿名管道的原理与实现

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 进程间通信基础认知 * 1.1 进程间通信的核心目的 * 1.2 进程间通信的发展与分类 * 二. 管道的基础概念 * 2.1 管道的定义 * 2.2 管道的核心特性(最后总结部分的图片里更全点,可以着重看那个) * 三. 匿名管道的创建与 API * 3.1 匿名管道的创建函数 * 3.2 匿名管道的简单使用示例 * 四. 基于 fork 的匿名管道跨进程通信 * 4.1 fork 共享管道的核心原理 * 4.2

By Ne0inhk