0
点赞
收藏
分享

微信扫一扫

JavaScript中的Map与Set:高效数据结构的应用场景

1. Map与Set基础概念

Map:键值对集合

// 创建Map
const userMap = new Map();

// 添加键值对
userMap.set('user1', { name: '张三', age: 25 });
userMap.set('user2', { name: '李四', age: 30 });

// 获取值
console.log(userMap.get('user1')); // { name: '张三', age: 25 }

// 检查是否存在
console.log(userMap.has('user1')); // true

// 删除
userMap.delete('user2');

Set:唯一值集合

// 创建Set
const uniqueNumbers = new Set([1, 2, 3, 2, 4, 1]);

// 添加元素
uniqueNumbers.add(5);
uniqueNumbers.add(3); // 重复元素不会被添加

// 检查是否存在
console.log(uniqueNumbers.has(3)); // true

// 删除元素
uniqueNumbers.delete(1);

// 转换为数组
console.log([...uniqueNumbers]); // [2, 3, 4, 5]

2. Map与Object的对比

性能对比

// 测试Map vs Object性能
function performanceTest() {
    const map = new Map();
    const obj = {};
    
    const keys = Array.from({length: 10000}, (_, i) => `key${i}`);
    
    // Map性能测试
    console.time('Map set');
    keys.forEach(key => map.set(key, key));
    console.timeEnd('Map set');
    
    console.time('Map get');
    keys.forEach(key => map.get(key));
    console.timeEnd('Map get');
    
    // Object性能测试
    console.time('Object set');
    keys.forEach(key => obj[key] = key);
    console.timeEnd('Object set');
    
    console.time('Object get');
    keys.forEach(key => obj[key]);
    console.timeEnd('Object get');
}

主要优势对比

特性 Map Object
键类型 任意类型 字符串/符号
性能 O(1) O(1)但有哈希冲突
迭代 原生支持 需要Object.keys()等
大小 size属性 需要手动计算

3. 实际应用场景

场景1:缓存系统

class Cache {
    constructor(maxSize = 100) {
        this.cache = new Map();
        this.maxSize = maxSize;
    }
    
    get(key) {
        if (this.cache.has(key)) {
            // LRU策略:将访问的项移到最后
            const value = this.cache.get(key);
            this.cache.delete(key);
            this.cache.set(key, value);
            return value;
        }
        return null;
    }
    
    set(key, value) {
        if (this.cache.size >= this.maxSize) {
            // 删除最老的项
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }
        this.cache.set(key, value);
    }
    
    clear() {
        this.cache.clear();
    }
}

// 使用示例
const cache = new Cache(3);
cache.set('data1', {id: 1, content: '...'});
cache.set('data2', {id: 2, content: '...'});

场景2:数据去重

// 数组去重
function uniqueArray(arr) {
    return [...new Set(arr)];
}

// 对象数组去重(基于特定属性)
function uniqueByProperty(arr, property) {
    const seen = new Set();
    return arr.filter(item => {
        const value = item[property];
        if (seen.has(value)) {
            return false;
        }
        seen.add(value);
        return true;
    });
}

// 使用示例
const numbers = [1, 2, 2, 3, 4, 4, 5];
console.log(uniqueArray(numbers)); // [1, 2, 3, 4, 5]

const users = [
    {id: 1, name: '张三'},
    {id: 2, name: '李四'},
    {id: 1, name: '张三'}
];
console.log(uniqueByProperty(users, 'id'));

场景3:频率统计

class WordCounter {
    constructor() {
        this.wordCount = new Map();
    }
    
    countWords(text) {
        const words = text.toLowerCase().split(/\s+/);
        
        words.forEach(word => {
            const cleanWord = word.replace(/[^\w]/g, '');
            if (cleanWord) {
                this.wordCount.set(
                    cleanWord, 
                    (this.wordCount.get(cleanWord) || 0) + 1
                );
            }
        });
    }
    
    getTopWords(n = 10) {
        return [...this.wordCount.entries()]
            .sort((a, b) => b[1] - a[1])
            .slice(0, n);
    }
    
    getWordCount(word) {
        return this.wordCount.get(word.toLowerCase()) || 0;
    }
}

// 使用示例
const counter = new WordCounter();
counter.countWords("hello world hello javascript world");
console.log(counter.getTopWords(3)); // [['hello', 2], ['world', 2], ['javascript', 1]]

场景4:关系映射

class Graph {
    constructor() {
        this.adjacencyList = new Map();
    }
    
    addVertex(vertex) {
        if (!this.adjacencyList.has(vertex)) {
            this.adjacencyList.set(vertex, new Set());
        }
    }
    
    addEdge(v1, v2) {
        this.addVertex(v1);
        this.addVertex(v2);
        this.adjacencyList.get(v1).add(v2);
        this.adjacencyList.get(v2).add(v1);
    }
    
    getNeighbors(vertex) {
        return this.adjacencyList.get(vertex) || new Set();
    }
    
    hasPath(start, end) {
        const visited = new Set();
        const queue = [start];
        
        while (queue.length > 0) {
            const current = queue.shift();
            if (current === end) return true;
            
            if (!visited.has(current)) {
                visited.add(current);
                const neighbors = this.getNeighbors(current);
                neighbors.forEach(neighbor => {
                    if (!visited.has(neighbor)) {
                        queue.push(neighbor);
                    }
                });
            }
        }
        
        return false;
    }
}

// 使用示例
const socialNetwork = new Graph();
socialNetwork.addEdge('Alice', 'Bob');
socialNetwork.addEdge('Bob', 'Charlie');
socialNetwork.addEdge('Alice', 'David');
console.log(socialNetwork.hasPath('Alice', 'Charlie')); // true

4. 高级应用技巧

迭代与遍历

// Map的遍历方式
const userMap = new Map([
    ['user1', {name: '张三', age: 25}],
    ['user2', {name: '李四', age: 30}],
    ['user3', {name: '王五', age: 28}]
]);

// 遍历键值对
for (const [key, value] of userMap) {
    console.log(`${key}: ${value.name}`);
}

// 遍历键
for (const key of userMap.keys()) {
    console.log(key);
}

// 遍历值
for (const value of userMap.values()) {
    console.log(value.name);
}

// 使用forEach
userMap.forEach((value, key) => {
    console.log(`${key}: ${value.age}`);
});

复合数据结构

// Map嵌套Set
class TagManager {
    constructor() {
        this.tagToItems = new Map(); // tag -> Set of item IDs
        this.itemToTags = new Map(); // item ID -> Set of tags
    }
    
    addTag(item, tag) {
        // 添加到tag->items映射
        if (!this.tagToItems.has(tag)) {
            this.tagToItems.set(tag, new Set());
        }
        this.tagToItems.get(tag).add(item);
        
        // 添加到item->tags映射
        if (!this.itemToTags.has(item)) {
            this.itemToTags.set(item, new Set());
        }
        this.itemToTags.get(item).add(tag);
    }
    
    getItemsByTag(tag) {
        return this.tagToItems.get(tag) || new Set();
    }
    
    getTagsByItem(item) {
        return this.itemToTags.get(item) || new Set();
    }
    
    removeTag(item, tag) {
        const itemTags = this.itemToTags.get(item);
        const tagItems = this.tagToItems.get(tag);
        
        if (itemTags) itemTags.delete(tag);
        if (tagItems) tagItems.delete(item);
        
        // 清理空的Set
        if (tagItems && tagItems.size === 0) {
            this.tagToItems.delete(tag);
        }
    }
}

// 使用示例
const tagManager = new TagManager();
tagManager.addTag('post1', 'javascript');
tagManager.addTag('post1', 'web');
tagManager.addTag('post2', 'javascript');
console.log([...tagManager.getItemsByTag('javascript')]); // ['post1', 'post2']

性能优化技巧

// 批量操作优化
class BatchProcessor {
    constructor() {
        this.data = new Map();
    }
    
    // 批量设置
    batchSet(entries) {
        // 使用构造函数批量创建,比逐个set更高效
        const newMap = new Map([...this.data, ...entries]);
        this.data = newMap;
    }
    
    // 批量删除
    batchDelete(keys) {
        const newMap = new Map(this.data);
        keys.forEach(key => newMap.delete(key));
        this.data = newMap;
    }
    
    // 条件过滤
    filter(predicate) {
        const result = new Map();
        for (const [key, value] of this.data) {
            if (predicate(value, key)) {
                result.set(key, value);
            }
        }
        return result;
    }
}

// 内存优化:弱引用
const weakMap = new WeakMap();
const weakSet = new WeakSet();

// 适用于临时关联数据,不会阻止垃圾回收
function createPrivateData() {
    const privateData = new WeakMap();
    
    return class {
        constructor(data) {
            privateData.set(this, data);
        }
        
        getData() {
            return privateData.get(this);
        }
    };
}

5. 最佳实践建议

何时使用Map

// ✅ 适合使用Map的场景
// 1. 键是对象或非字符串类型
const objectKeys = new Map();
const obj1 = {id: 1};
const obj2 = {id: 2};
objectKeys.set(obj1, 'value1');
objectKeys.set(obj2, 'value2');

// 2. 需要频繁增删操作
const frequentUpdates = new Map();
// Map的增删操作性能优于Object

// 3. 需要知道集合大小
console.log(frequentUpdates.size); // 直接获取

// 4. 需要保持插入顺序
// Map保持插入顺序,Object在某些情况下不保证

何时使用Set

// ✅ 适合使用Set的场景
// 1. 需要去重
const uniqueUsers = new Set(userIds);

// 2. 需要快速查找
const allowedRoles = new Set(['admin', 'editor', 'viewer']);
if (allowedRoles.has(userRole)) {
    // 快速检查
}

// 3. 集合运算
function setOperations(set1, set2) {
    // 并集
    const union = new Set([...set1, ...set2]);
    
    // 交集
    const intersection = new Set([...set1].filter(x => set2.has(x)));
    
    // 差集
    const difference = new Set([...set1].filter(x => !set2.has(x)));
    
    return { union, intersection, difference };
}

注意事项

// ❌ 常见错误
// 1. 使用Object作为Map的替代
const badMap = {}; // 当键是变量时容易出错
badMap[someObject] = 'value'; // 实际上是badMap['[object Object]']

// 2. 忘记处理undefined
const map = new Map();
map.set('key', undefined);
console.log(map.has('key')); // true,但值是undefined
console.log(map.get('key')); // undefined

// 3. 大量数据时的内存考虑
// 对于超大数据集,考虑分片或外部存储

总结

Map和Set是JavaScript中强大的数据结构,它们在以下场景特别有用:

  • Map:适合键值对存储,特别是键不是字符串的情况,提供更好的性能和功能
  • Set:适合去重和成员检查,提供O(1)的查找性能
举报

相关推荐

0 条评论