• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.collect;
18 
19 import static java.util.Objects.requireNonNull;
20 
21 import com.google.common.annotations.GwtIncompatible;
22 import com.google.common.annotations.J2ktIncompatible;
23 import com.google.common.annotations.VisibleForTesting;
24 import com.google.errorprone.annotations.CanIgnoreReturnValue;
25 import java.util.Arrays;
26 import java.util.LinkedHashMap;
27 import java.util.Map;
28 import javax.annotation.CheckForNull;
29 import org.checkerframework.checker.nullness.qual.Nullable;
30 
31 /**
32  * CompactLinkedHashMap is an implementation of a Map with insertion or LRU iteration order,
33  * maintained with a doubly linked list through the entries. All optional operations (put and
34  * remove) are supported. Null keys and values are supported.
35  *
36  * <p>{@code containsKey(k)}, {@code put(k, v)} and {@code remove(k)} are all (expected and
37  * amortized) constant time operations. Expected in the hashtable sense (depends on the hash
38  * function doing a good job of distributing the elements to the buckets to a distribution not far
39  * from uniform), and amortized since some operations can trigger a hash table resize.
40  *
41  * <p>As compared with {@link java.util.LinkedHashMap}, this structure places significantly reduced
42  * load on the garbage collector by only using a constant number of internal objects.
43  *
44  * <p>This class should not be assumed to be universally superior to {@code
45  * java.util.LinkedHashMap}. Generally speaking, this class reduces object allocation and memory
46  * consumption at the price of moderately increased constant factors of CPU. Only use this class
47  * when there is a specific reason to prioritize memory over CPU.
48  *
49  * @author Louis Wasserman
50  */
51 @J2ktIncompatible // no support for access-order mode in LinkedHashMap delegate
52 @GwtIncompatible // not worth using in GWT for now
53 @ElementTypesAreNonnullByDefault
54 class CompactLinkedHashMap<K extends @Nullable Object, V extends @Nullable Object>
55     extends CompactHashMap<K, V> {
56   // TODO(lowasser): implement removeEldestEntry so this can be used as a drop-in replacement
57 
58   /** Creates an empty {@code CompactLinkedHashMap} instance. */
59   public static <K extends @Nullable Object, V extends @Nullable Object>
create()60       CompactLinkedHashMap<K, V> create() {
61     return new CompactLinkedHashMap<>();
62   }
63 
64   /**
65    * Creates a {@code CompactLinkedHashMap} instance, with a high enough "initial capacity" that it
66    * <i>should</i> hold {@code expectedSize} elements without rebuilding internal data structures.
67    *
68    * @param expectedSize the number of elements you expect to add to the returned set
69    * @return a new, empty {@code CompactLinkedHashMap} with enough capacity to hold {@code
70    *     expectedSize} elements without resizing
71    * @throws IllegalArgumentException if {@code expectedSize} is negative
72    */
73   public static <K extends @Nullable Object, V extends @Nullable Object>
createWithExpectedSize(int expectedSize)74       CompactLinkedHashMap<K, V> createWithExpectedSize(int expectedSize) {
75     return new CompactLinkedHashMap<>(expectedSize);
76   }
77 
78   private static final int ENDPOINT = -2;
79 
80   /**
81    * Contains the link pointers corresponding with the entries, in the range of [0, size()). The
82    * high 32 bits of each long is the "prev" pointer, whereas the low 32 bits is the "succ" pointer
83    * (pointing to the next entry in the linked list). The pointers in [size(), entries.length) are
84    * all "null" (UNSET).
85    *
86    * <p>A node with "prev" pointer equal to {@code ENDPOINT} is the first node in the linked list,
87    * and a node with "next" pointer equal to {@code ENDPOINT} is the last node.
88    */
89   @CheckForNull @VisibleForTesting transient long[] links;
90 
91   /** Pointer to the first node in the linked list, or {@code ENDPOINT} if there are no entries. */
92   private transient int firstEntry;
93 
94   /** Pointer to the last node in the linked list, or {@code ENDPOINT} if there are no entries. */
95   private transient int lastEntry;
96 
97   private final boolean accessOrder;
98 
CompactLinkedHashMap()99   CompactLinkedHashMap() {
100     this(CompactHashing.DEFAULT_SIZE);
101   }
102 
CompactLinkedHashMap(int expectedSize)103   CompactLinkedHashMap(int expectedSize) {
104     this(expectedSize, false);
105   }
106 
CompactLinkedHashMap(int expectedSize, boolean accessOrder)107   CompactLinkedHashMap(int expectedSize, boolean accessOrder) {
108     super(expectedSize);
109     this.accessOrder = accessOrder;
110   }
111 
112   @Override
init(int expectedSize)113   void init(int expectedSize) {
114     super.init(expectedSize);
115     this.firstEntry = ENDPOINT;
116     this.lastEntry = ENDPOINT;
117   }
118 
119   @Override
allocArrays()120   int allocArrays() {
121     int expectedSize = super.allocArrays();
122     this.links = new long[expectedSize];
123     return expectedSize;
124   }
125 
126   @Override
createHashFloodingResistantDelegate(int tableSize)127   Map<K, V> createHashFloodingResistantDelegate(int tableSize) {
128     return new LinkedHashMap<>(tableSize, 1.0f, accessOrder);
129   }
130 
131   @Override
132   @CanIgnoreReturnValue
convertToHashFloodingResistantImplementation()133   Map<K, V> convertToHashFloodingResistantImplementation() {
134     Map<K, V> result = super.convertToHashFloodingResistantImplementation();
135     links = null;
136     return result;
137   }
138 
139   /*
140    * For discussion of the safety of the following methods for operating on predecessors and
141    * successors, see the comments near the end of CompactHashMap, noting that the methods here call
142    * link(), which is defined at the end of this file.
143    */
144 
getPredecessor(int entry)145   private int getPredecessor(int entry) {
146     return ((int) (link(entry) >>> 32)) - 1;
147   }
148 
149   @Override
getSuccessor(int entry)150   int getSuccessor(int entry) {
151     return ((int) link(entry)) - 1;
152   }
153 
setSuccessor(int entry, int succ)154   private void setSuccessor(int entry, int succ) {
155     long succMask = (~0L) >>> 32;
156     setLink(entry, (link(entry) & ~succMask) | ((succ + 1) & succMask));
157   }
158 
setPredecessor(int entry, int pred)159   private void setPredecessor(int entry, int pred) {
160     long predMask = ~0L << 32;
161     setLink(entry, (link(entry) & ~predMask) | ((long) (pred + 1) << 32));
162   }
163 
setSucceeds(int pred, int succ)164   private void setSucceeds(int pred, int succ) {
165     if (pred == ENDPOINT) {
166       firstEntry = succ;
167     } else {
168       setSuccessor(pred, succ);
169     }
170 
171     if (succ == ENDPOINT) {
172       lastEntry = pred;
173     } else {
174       setPredecessor(succ, pred);
175     }
176   }
177 
178   @Override
insertEntry( int entryIndex, @ParametricNullness K key, @ParametricNullness V value, int hash, int mask)179   void insertEntry(
180       int entryIndex, @ParametricNullness K key, @ParametricNullness V value, int hash, int mask) {
181     super.insertEntry(entryIndex, key, value, hash, mask);
182     setSucceeds(lastEntry, entryIndex);
183     setSucceeds(entryIndex, ENDPOINT);
184   }
185 
186   @Override
accessEntry(int index)187   void accessEntry(int index) {
188     if (accessOrder) {
189       // delete from previous position...
190       setSucceeds(getPredecessor(index), getSuccessor(index));
191       // ...and insert at the end.
192       setSucceeds(lastEntry, index);
193       setSucceeds(index, ENDPOINT);
194       incrementModCount();
195     }
196   }
197 
198   @Override
moveLastEntry(int dstIndex, int mask)199   void moveLastEntry(int dstIndex, int mask) {
200     int srcIndex = size() - 1;
201     super.moveLastEntry(dstIndex, mask);
202 
203     setSucceeds(getPredecessor(dstIndex), getSuccessor(dstIndex));
204     if (dstIndex < srcIndex) {
205       setSucceeds(getPredecessor(srcIndex), dstIndex);
206       setSucceeds(dstIndex, getSuccessor(srcIndex));
207     }
208     setLink(srcIndex, 0);
209   }
210 
211   @Override
resizeEntries(int newCapacity)212   void resizeEntries(int newCapacity) {
213     super.resizeEntries(newCapacity);
214     links = Arrays.copyOf(requireLinks(), newCapacity);
215   }
216 
217   @Override
firstEntryIndex()218   int firstEntryIndex() {
219     return firstEntry;
220   }
221 
222   @Override
adjustAfterRemove(int indexBeforeRemove, int indexRemoved)223   int adjustAfterRemove(int indexBeforeRemove, int indexRemoved) {
224     return (indexBeforeRemove >= size()) ? indexRemoved : indexBeforeRemove;
225   }
226 
227   @Override
clear()228   public void clear() {
229     if (needsAllocArrays()) {
230       return;
231     }
232     this.firstEntry = ENDPOINT;
233     this.lastEntry = ENDPOINT;
234     if (links != null) {
235       Arrays.fill(links, 0, size(), 0);
236     }
237     super.clear();
238   }
239 
240   /*
241    * For discussion of the safety of the following methods, see the comments near the end of
242    * CompactHashMap.
243    */
244 
requireLinks()245   private long[] requireLinks() {
246     return requireNonNull(links);
247   }
248 
link(int i)249   private long link(int i) {
250     return requireLinks()[i];
251   }
252 
setLink(int i, long value)253   private void setLink(int i, long value) {
254     requireLinks()[i] = value;
255   }
256 
257   /*
258    * We don't define getPredecessor+getSuccessor and setPredecessor+setSuccessor here because
259    * they're defined above -- including logic to add and subtract 1 to map between the values stored
260    * in the predecessor/successor arrays and the indexes in the elements array that they identify.
261    */
262 }
263