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