WXL
3 天以前 9bce51f651aad297ef9eb6df832bfdaf1de05d84
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
    MIT License http://www.opensource.org/licenses/mit-license.php
    Author Tobias Koppers @sokra
*/
 
"use strict";
 
/**
 * The StackedCacheMap is a data structure designed as an alternative to a Map
 * in situations where you need to handle multiple item additions and
 * frequently access the largest map.
 *
 * It is particularly optimized for efficiently adding multiple items
 * at once, which can be achieved using the `addAll` method.
 *
 * It has a fallback Map that is used when the map to be added is mutable.
 *
 * Note: `delete` and `has` are not supported for performance reasons.
 * @example
 * ```js
 * const map = new StackedCacheMap();
 * map.addAll(new Map([["a", 1], ["b", 2]]), true);
 * map.addAll(new Map([["c", 3], ["d", 4]]), true);
 * map.get("a"); // 1
 * map.get("d"); // 4
 * for (const [key, value] of map) {
 *         console.log(key, value);
 * }
 * ```
 * @template K
 * @template V
 */
class StackedCacheMap {
    /**
     * Initializes the mutable fallback map and the stack of immutable cache
     * layers.
     */
    constructor() {
        /** @type {Map<K, V>} */
        this.map = new Map();
        /** @type {ReadonlyMap<K, V>[]} */
        this.stack = [];
    }
 
    /**
     * Adds another cache layer. Immutable maps are retained by reference and
     * reordered so larger layers are checked first, while mutable maps are
     * copied into the fallback map.
     * @param {ReadonlyMap<K, V>} map map to add
     * @param {boolean=} immutable if 'map' is immutable and StackedCacheMap can keep referencing it
     */
    addAll(map, immutable) {
        if (immutable) {
            this.stack.push(map);
 
            // largest map should go first
            for (let i = this.stack.length - 1; i > 0; i--) {
                const beforeLast = this.stack[i - 1];
                if (beforeLast.size >= map.size) break;
                this.stack[i] = beforeLast;
                this.stack[i - 1] = map;
            }
        } else {
            for (const [key, value] of map) {
                this.map.set(key, value);
            }
        }
    }
 
    /**
     * Stores or overrides a value in the mutable fallback map.
     * @param {K} item the key of the element to add
     * @param {V} value the value of the element to add
     * @returns {void}
     */
    set(item, value) {
        this.map.set(item, value);
    }
 
    /**
     * Rejects deletions because this data structure is optimized for append-only
     * cache layers.
     * @param {K} item the item to delete
     * @returns {void}
     */
    delete(item) {
        throw new Error("Items can't be deleted from a StackedCacheMap");
    }
 
    /**
     * Rejects `has` checks because they would force the same layered lookup work
     * as `get` without returning the cached value.
     * @param {K} item the item to test
     * @returns {boolean} true if the item exists in this set
     */
    has(item) {
        throw new Error(
            "Checking StackedCacheMap.has before reading is inefficient, use StackedCacheMap.get and check for undefined"
        );
    }
 
    /**
     * Looks up a key by scanning immutable cache layers first and then the
     * mutable fallback map.
     * @param {K} item the key of the element to return
     * @returns {V | undefined} the value of the element
     */
    get(item) {
        for (const map of this.stack) {
            const value = map.get(item);
            if (value !== undefined) return value;
        }
        return this.map.get(item);
    }
 
    /**
     * Removes every cache layer and clears the mutable fallback map.
     */
    clear() {
        this.stack.length = 0;
        this.map.clear();
    }
 
    /**
     * Returns the total number of entries across the fallback map and all stacked
     * cache layers.
     * @returns {number} size of the map
     */
    get size() {
        let size = this.map.size;
        for (const map of this.stack) {
            size += map.size;
        }
        return size;
    }
 
    /**
     * Iterates over the fallback map first and then each stacked cache layer.
     * @returns {Iterator<[K, V]>} iterator
     */
    [Symbol.iterator]() {
        const iterators = this.stack.map((map) => map[Symbol.iterator]());
        let current = this.map[Symbol.iterator]();
        return {
            next() {
                let result = current.next();
                while (result.done && iterators.length > 0) {
                    current = /** @type {MapIterator<[K, V]>} */ (iterators.pop());
                    result = current.next();
                }
                return result;
            }
        };
    }
}
 
module.exports = StackedCacheMap;