MST

星途 面试题库

面试题:JavaScript Set和Map与其他数据结构的结合优化及内存管理

在复杂的JavaScript应用中,有时需要将Set或Map与其他数据结构(如数组、对象等)结合使用以优化性能。请举例说明如何结合不同数据结构实现高效的缓存机制,同时考虑内存管理问题。例如,当缓存数据量较大时,如何避免内存泄漏,以及如何根据业务需求合理调整缓存策略。此外,阐述在Node.js环境下,Set和Map在处理高并发场景数据时可能面临的挑战及解决方案。
37.0万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

结合不同数据结构实现高效缓存机制

  1. 使用Map和数组实现LRU缓存
    • 原理:LRU(Least Recently Used)缓存策略是当缓存满时,优先淘汰最久未使用的数据。
    • 代码示例
class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.cache = new Map();
        this.order = [];
    }

    get(key) {
        if (this.cache.has(key)) {
            this.order.splice(this.order.indexOf(key), 1);
            this.order.push(key);
            return this.cache.get(key);
        }
        return -1;
    }

    put(key, value) {
        if (this.cache.has(key)) {
            this.cache.set(key, value);
            this.order.splice(this.order.indexOf(key), 1);
        } else if (this.cache.size >= this.capacity) {
            const leastUsed = this.order.shift();
            this.cache.delete(leastUsed);
        }
        this.cache.set(key, value);
        this.order.push(key);
    }
}
  • 说明:这里使用Map来存储键值对,因为Map的查找和删除操作时间复杂度为O(1)。数组用来记录访问顺序,通过数组操作来维护LRU顺序。
  1. 使用Set和对象实现缓存去重
    • 原理:在缓存数据时,有时需要确保缓存的数据不重复。Set可以高效地检查元素是否存在,而对象可以存储具体的数据。
    • 代码示例
const cacheSet = new Set();
const cacheObject = {};

function addToCache(key, value) {
    if (!cacheSet.has(key)) {
        cacheSet.add(key);
        cacheObject[key] = value;
    }
}

function getFromCache(key) {
    if (cacheSet.has(key)) {
        return cacheObject[key];
    }
    return null;
}
  • 说明Set用于快速判断某个键是否已经在缓存中,对象用于存储实际的缓存值。

内存管理问题

  1. 避免内存泄漏
    • 及时清理过期数据:在缓存中设置过期时间,定期检查并删除过期的数据。例如,在LRU缓存中,可以在put操作时记录每个键值对的时间戳,在getput操作时检查并删除过期数据。
class ExpiringLRUCache {
    constructor(capacity, ttl) {
        this.capacity = capacity;
        this.ttl = ttl;
        this.cache = new Map();
        this.order = [];
        this.timestamps = new Map();
    }

    get(key) {
        if (this.cache.has(key)) {
            const now = Date.now();
            if (now - this.timestamps.get(key) > this.ttl) {
                this.cache.delete(key);
                this.order.splice(this.order.indexOf(key), 1);
                this.timestamps.delete(key);
                return -1;
            }
            this.order.splice(this.order.indexOf(key), 1);
            this.order.push(key);
            this.timestamps.set(key, now);
            return this.cache.get(key);
        }
        return -1;
    }

    put(key, value) {
        const now = Date.now();
        if (this.cache.has(key)) {
            this.cache.set(key, value);
            this.order.splice(this.order.indexOf(key), 1);
            this.timestamps.set(key, now);
        } else if (this.cache.size >= this.capacity) {
            const leastUsed = this.order.shift();
            this.cache.delete(leastUsed);
            this.timestamps.delete(leastUsed);
        }
        this.cache.set(key, value);
        this.order.push(key);
        this.timestamps.set(key, now);
    }
}
  • 解除引用:当从缓存中删除数据时,确保所有对该数据的引用都被解除,特别是当缓存的数据是复杂对象时。例如,在LRUCachedelete操作中,不仅要从Map中删除键值对,还要确保没有其他地方引用该值。
  1. 根据业务需求调整缓存策略
    • 读多写少场景:可以使用更复杂的缓存策略,如LFU(Least Frequently Used),这种策略记录每个数据的访问频率,优先淘汰访问频率最低的数据。
    • 写多读少场景:可以适当降低缓存容量,减少内存占用,因为数据变化频繁,缓存的命中率可能较低。

Node.js环境下Set和Map在高并发场景的挑战及解决方案

  1. 挑战
    • 线程安全问题:Node.js是单线程的,但在高并发场景下,多个异步操作可能同时访问和修改SetMap,导致数据不一致。例如,在一个Web应用中,多个HTTP请求可能同时尝试更新缓存(SetMap)。
    • 性能瓶颈:虽然SetMap在单个操作上性能较好,但在高并发大量操作时,可能会因为单线程执行而出现性能瓶颈。
  2. 解决方案
    • 使用锁机制:可以使用mutex(互斥锁)来确保同一时间只有一个操作可以访问和修改SetMap。例如,使用async - mutex库:
const Mutex = require('async - mutex').Mutex;
const mySet = new Set();
const mutex = new Mutex();

async function addToSet(value) {
    const release = await mutex.acquire();
    try {
        mySet.add(value);
    } finally {
        release();
    }
}

async function getSetSize() {
    const release = await mutex.acquire();
    try {
        return mySet.size;
    } finally {
        release();
    }
}
  • 分区策略:将SetMap分成多个部分(分区),不同的操作可以并行处理不同的分区,减少竞争。例如,根据键的哈希值将缓存数据分配到不同的Map实例中,每个实例由独立的异步任务管理。