| /** |
| * Copyright 2010 The Apache Software Foundation |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.hadoop.hbase.regionserver; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.hadoop.hbase.io.HeapSize; |
| import org.apache.hadoop.hbase.util.Bytes; |
| import org.apache.hadoop.hbase.util.ClassSize; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * The LruHashMap is a memory-aware HashMap with a configurable maximum |
| * memory footprint. |
| * <p> |
| * It maintains an ordered list of all entries in the map ordered by |
| * access time. When space needs to be freed becase the maximum has been |
| * reached, or the application has asked to free memory, entries will be |
| * evicted according to an LRU (least-recently-used) algorithm. That is, |
| * those entries which have not been accessed the longest will be evicted |
| * first. |
| * <p> |
| * Both the Key and Value Objects used for this class must extend |
| * <code>HeapSize</code> in order to track heap usage. |
| * <p> |
| * This class contains internal synchronization and is thread-safe. |
| */ |
| public class LruHashMap<K extends HeapSize, V extends HeapSize> |
| implements HeapSize, Map<K,V> { |
| |
| static final Log LOG = LogFactory.getLog(LruHashMap.class); |
| |
| /** The default size (in bytes) of the LRU */ |
| private static final long DEFAULT_MAX_MEM_USAGE = 50000; |
| /** The default capacity of the hash table */ |
| private static final int DEFAULT_INITIAL_CAPACITY = 16; |
| /** The maxmum capacity of the hash table */ |
| private static final int MAXIMUM_CAPACITY = 1 << 30; |
| /** The default load factor to use */ |
| private static final float DEFAULT_LOAD_FACTOR = 0.75f; |
| |
| /** Memory overhead of this Object (for HeapSize) */ |
| private static final int OVERHEAD = 5 * Bytes.SIZEOF_LONG + |
| 2 * Bytes.SIZEOF_INT + 2 * Bytes.SIZEOF_FLOAT + 3 * ClassSize.REFERENCE + |
| 1 * ClassSize.ARRAY; |
| |
| /** Load factor allowed (usually 75%) */ |
| private final float loadFactor; |
| /** Number of key/vals in the map */ |
| private int size; |
| /** Size at which we grow hash */ |
| private int threshold; |
| /** Entries in the map */ |
| private Entry [] entries; |
| |
| /** Pointer to least recently used entry */ |
| private Entry<K,V> headPtr; |
| /** Pointer to most recently used entry */ |
| private Entry<K,V> tailPtr; |
| |
| /** Maximum memory usage of this map */ |
| private long memTotal = 0; |
| /** Amount of available memory */ |
| private long memFree = 0; |
| |
| /** Number of successful (found) get() calls */ |
| private long hitCount = 0; |
| /** Number of unsuccessful (not found) get() calls */ |
| private long missCount = 0; |
| |
| /** |
| * Constructs a new, empty map with the specified initial capacity, |
| * load factor, and maximum memory usage. |
| * |
| * @param initialCapacity the initial capacity |
| * @param loadFactor the load factor |
| * @param maxMemUsage the maximum total memory usage |
| * @throws IllegalArgumentException if the initial capacity is less than one |
| * @throws IllegalArgumentException if the initial capacity is greater than |
| * the maximum capacity |
| * @throws IllegalArgumentException if the load factor is <= 0 |
| * @throws IllegalArgumentException if the max memory usage is too small |
| * to support the base overhead |
| */ |
| public LruHashMap(int initialCapacity, float loadFactor, |
| long maxMemUsage) { |
| if (initialCapacity < 1) { |
| throw new IllegalArgumentException("Initial capacity must be > 0"); |
| } |
| if (initialCapacity > MAXIMUM_CAPACITY) { |
| throw new IllegalArgumentException("Initial capacity is too large"); |
| } |
| if (loadFactor <= 0 || Float.isNaN(loadFactor)) { |
| throw new IllegalArgumentException("Load factor must be > 0"); |
| } |
| if (maxMemUsage <= (OVERHEAD + initialCapacity * ClassSize.REFERENCE)) { |
| throw new IllegalArgumentException("Max memory usage too small to " + |
| "support base overhead"); |
| } |
| |
| /** Find a power of 2 >= initialCapacity */ |
| int capacity = calculateCapacity(initialCapacity); |
| this.loadFactor = loadFactor; |
| this.threshold = calculateThreshold(capacity,loadFactor); |
| this.entries = new Entry[capacity]; |
| this.memFree = maxMemUsage; |
| this.memTotal = maxMemUsage; |
| init(); |
| } |
| |
| /** |
| * Constructs a new, empty map with the specified initial capacity and |
| * load factor, and default maximum memory usage. |
| * |
| * @param initialCapacity the initial capacity |
| * @param loadFactor the load factor |
| * @throws IllegalArgumentException if the initial capacity is less than one |
| * @throws IllegalArgumentException if the initial capacity is greater than |
| * the maximum capacity |
| * @throws IllegalArgumentException if the load factor is <= 0 |
| */ |
| public LruHashMap(int initialCapacity, float loadFactor) { |
| this(initialCapacity, loadFactor, DEFAULT_MAX_MEM_USAGE); |
| } |
| |
| /** |
| * Constructs a new, empty map with the specified initial capacity and |
| * with the default load factor and maximum memory usage. |
| * |
| * @param initialCapacity the initial capacity |
| * @throws IllegalArgumentException if the initial capacity is less than one |
| * @throws IllegalArgumentException if the initial capacity is greater than |
| * the maximum capacity |
| */ |
| public LruHashMap(int initialCapacity) { |
| this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_MAX_MEM_USAGE); |
| } |
| |
| /** |
| * Constructs a new, empty map with the specified maximum memory usage |
| * and with default initial capacity and load factor. |
| * |
| * @param maxMemUsage the maximum total memory usage |
| * @throws IllegalArgumentException if the max memory usage is too small |
| * to support the base overhead |
| */ |
| public LruHashMap(long maxMemUsage) { |
| this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, |
| maxMemUsage); |
| } |
| |
| /** |
| * Constructs a new, empty map with the default initial capacity, |
| * load factor and maximum memory usage. |
| */ |
| public LruHashMap() { |
| this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, |
| DEFAULT_MAX_MEM_USAGE); |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * Get the currently available memory for this LRU in bytes. |
| * This is (maxAllowed - currentlyUsed). |
| * |
| * @return currently available bytes |
| */ |
| public long getMemFree() { |
| return memFree; |
| } |
| |
| /** |
| * Get the maximum memory allowed for this LRU in bytes. |
| * |
| * @return maximum allowed bytes |
| */ |
| public long getMemMax() { |
| return memTotal; |
| } |
| |
| /** |
| * Get the currently used memory for this LRU in bytes. |
| * |
| * @return currently used memory in bytes |
| */ |
| public long getMemUsed() { |
| return (memTotal - memFree); // FindBugs IS2_INCONSISTENT_SYNC |
| } |
| |
| /** |
| * Get the number of hits to the map. This is the number of times |
| * a call to get() returns a matched key. |
| * |
| * @return number of hits |
| */ |
| public long getHitCount() { |
| return hitCount; |
| } |
| |
| /** |
| * Get the number of misses to the map. This is the number of times |
| * a call to get() returns null. |
| * |
| * @return number of misses |
| */ |
| public long getMissCount() { |
| return missCount; // FindBugs IS2_INCONSISTENT_SYNC |
| } |
| |
| /** |
| * Get the hit ratio. This is the number of hits divided by the |
| * total number of requests. |
| * |
| * @return hit ratio (double between 0 and 1) |
| */ |
| public double getHitRatio() { |
| return (double)((double)hitCount/ |
| ((double)(hitCount+missCount))); |
| } |
| |
| /** |
| * Free the requested amount of memory from the LRU map. |
| * |
| * This will do LRU eviction from the map until at least as much |
| * memory as requested is freed. This does not affect the maximum |
| * memory usage parameter. |
| * |
| * @param requestedAmount memory to free from LRU in bytes |
| * @return actual amount of memory freed in bytes |
| */ |
| public synchronized long freeMemory(long requestedAmount) throws Exception { |
| if(requestedAmount > (getMemUsed() - getMinimumUsage())) { |
| return clearAll(); |
| } |
| long freedMemory = 0; |
| while(freedMemory < requestedAmount) { |
| freedMemory += evictFromLru(); |
| } |
| return freedMemory; |
| } |
| |
| /** |
| * The total memory usage of this map |
| * |
| * @return memory usage of map in bytes |
| */ |
| public long heapSize() { |
| return (memTotal - memFree); |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * Retrieves the value associated with the specified key. |
| * |
| * If an entry is found, it is updated in the LRU as the most recently |
| * used (last to be evicted) entry in the map. |
| * |
| * @param key the key |
| * @return the associated value, or null if none found |
| * @throws NullPointerException if key is null |
| */ |
| public synchronized V get(Object key) { |
| checkKey((K)key); |
| int hash = hash(key); |
| int i = hashIndex(hash, entries.length); |
| Entry<K,V> e = entries[i]; |
| while (true) { |
| if (e == null) { |
| missCount++; |
| return null; |
| } |
| if (e.hash == hash && isEqual(key, e.key)) { |
| // Hit! Update position in LRU |
| hitCount++; |
| updateLru(e); |
| return e.value; |
| } |
| e = e.next; |
| } |
| } |
| |
| /** |
| * Insert a key-value mapping into the map. |
| * |
| * Entry will be inserted as the most recently used. |
| * |
| * Both the key and value are required to be Objects and must |
| * implement the HeapSize interface. |
| * |
| * @param key the key |
| * @param value the value |
| * @return the value that was previously mapped to this key, null if none |
| * @throws UnsupportedOperationException if either objects do not |
| * implement HeapSize |
| * @throws NullPointerException if the key or value is null |
| */ |
| public synchronized V put(K key, V value) { |
| checkKey(key); |
| checkValue(value); |
| int hash = hash(key); |
| int i = hashIndex(hash, entries.length); |
| |
| // For old values |
| for (Entry<K,V> e = entries[i]; e != null; e = e.next) { |
| if (e.hash == hash && isEqual(key, e.key)) { |
| V oldValue = e.value; |
| long memChange = e.replaceValue(value); |
| checkAndFreeMemory(memChange); |
| // If replacing an old value for this key, update in LRU |
| updateLru(e); |
| return oldValue; |
| } |
| } |
| long memChange = addEntry(hash, key, value, i); |
| checkAndFreeMemory(memChange); |
| return null; |
| } |
| |
| /** |
| * Deletes the mapping for the specified key if it exists. |
| * |
| * @param key the key of the entry to be removed from the map |
| * @return the value associated with the specified key, or null |
| * if no mapping exists. |
| */ |
| public synchronized V remove(Object key) { |
| Entry<K,V> e = removeEntryForKey((K)key); |
| if(e == null) return null; |
| // Add freed memory back to available |
| memFree += e.heapSize(); |
| return e.value; |
| } |
| |
| /** |
| * Gets the size (number of entries) of the map. |
| * |
| * @return size of the map |
| */ |
| public int size() { |
| return size; |
| } |
| |
| /** |
| * Checks whether the map is currently empty. |
| * |
| * @return true if size of map is zero |
| */ |
| public boolean isEmpty() { |
| return size == 0; |
| } |
| |
| /** |
| * Clears all entries from the map. |
| * |
| * This frees all entries, tracking memory usage along the way. |
| * All references to entries are removed so they can be GC'd. |
| */ |
| public synchronized void clear() { |
| memFree += clearAll(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * Checks whether there is a value in the map for the specified key. |
| * |
| * Does not affect the LRU. |
| * |
| * @param key the key to check |
| * @return true if the map contains a value for this key, false if not |
| * @throws NullPointerException if the key is null |
| */ |
| public synchronized boolean containsKey(Object key) { |
| checkKey((K)key); |
| int hash = hash(key); |
| int i = hashIndex(hash, entries.length); |
| Entry e = entries[i]; |
| while (e != null) { |
| if (e.hash == hash && isEqual(key, e.key)) |
| return true; |
| e = e.next; |
| } |
| return false; |
| } |
| |
| /** |
| * Checks whether this is a mapping which contains the specified value. |
| * |
| * Does not affect the LRU. This is an inefficient operation. |
| * |
| * @param value the value to check |
| * @return true if the map contains an entry for this value, false |
| * if not |
| * @throws NullPointerException if the value is null |
| */ |
| public synchronized boolean containsValue(Object value) { |
| checkValue((V)value); |
| Entry[] tab = entries; |
| for (int i = 0; i < tab.length ; i++) |
| for (Entry e = tab[i] ; e != null ; e = e.next) |
| if (value.equals(e.value)) |
| return true; |
| return false; |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * Enforces key constraints. Null keys are not permitted and key must |
| * implement HeapSize. It should not be necessary to verify the second |
| * constraint because that's enforced on instantiation? |
| * |
| * Can add other constraints in the future. |
| * |
| * @param key the key |
| * @throws NullPointerException if the key is null |
| * @throws UnsupportedOperationException if the key class does not |
| * implement the HeapSize interface |
| */ |
| private void checkKey(K key) { |
| if(key == null) { |
| throw new NullPointerException("null keys are not allowed"); |
| } |
| } |
| |
| /** |
| * Enforces value constraints. Null values are not permitted and value must |
| * implement HeapSize. It should not be necessary to verify the second |
| * constraint because that's enforced on instantiation? |
| * |
| * Can add other contraints in the future. |
| * |
| * @param value the value |
| * @throws NullPointerException if the value is null |
| * @throws UnsupportedOperationException if the value class does not |
| * implement the HeapSize interface |
| */ |
| private void checkValue(V value) { |
| if(value == null) { |
| throw new NullPointerException("null values are not allowed"); |
| } |
| } |
| |
| /** |
| * Returns the minimum memory usage of the base map structure. |
| * |
| * @return baseline memory overhead of object in bytes |
| */ |
| private long getMinimumUsage() { |
| return OVERHEAD + (entries.length * ClassSize.REFERENCE); |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * Evicts and frees based on LRU until at least as much memory as requested |
| * is available. |
| * |
| * @param memNeeded the amount of memory needed in bytes |
| */ |
| private void checkAndFreeMemory(long memNeeded) { |
| while(memFree < memNeeded) { |
| evictFromLru(); |
| } |
| memFree -= memNeeded; |
| } |
| |
| /** |
| * Evicts based on LRU. This removes all references and updates available |
| * memory. |
| * |
| * @return amount of memory freed in bytes |
| */ |
| private long evictFromLru() { |
| long freed = headPtr.heapSize(); |
| memFree += freed; |
| removeEntry(headPtr); |
| return freed; |
| } |
| |
| /** |
| * Moves the specified entry to the most recently used slot of the |
| * LRU. This is called whenever an entry is fetched. |
| * |
| * @param e entry that was accessed |
| */ |
| private void updateLru(Entry<K,V> e) { |
| Entry<K,V> prev = e.getPrevPtr(); |
| Entry<K,V> next = e.getNextPtr(); |
| if(next != null) { |
| if(prev != null) { |
| prev.setNextPtr(next); |
| next.setPrevPtr(prev); |
| } else { |
| headPtr = next; |
| headPtr.setPrevPtr(null); |
| } |
| e.setNextPtr(null); |
| e.setPrevPtr(tailPtr); |
| tailPtr.setNextPtr(e); |
| tailPtr = e; |
| } |
| } |
| |
| /** |
| * Removes the specified entry from the map and LRU structure. |
| * |
| * @param entry entry to be removed |
| */ |
| private void removeEntry(Entry<K,V> entry) { |
| K k = entry.key; |
| int hash = entry.hash; |
| int i = hashIndex(hash, entries.length); |
| Entry<K,V> prev = entries[i]; |
| Entry<K,V> e = prev; |
| |
| while (e != null) { |
| Entry<K,V> next = e.next; |
| if (e.hash == hash && isEqual(k, e.key)) { |
| size--; |
| if (prev == e) { |
| entries[i] = next; |
| } else { |
| prev.next = next; |
| } |
| |
| Entry<K,V> prevPtr = e.getPrevPtr(); |
| Entry<K,V> nextPtr = e.getNextPtr(); |
| |
| if(prevPtr != null && nextPtr != null) { |
| prevPtr.setNextPtr(nextPtr); |
| nextPtr.setPrevPtr(prevPtr); |
| } else if(prevPtr != null) { |
| tailPtr = prevPtr; |
| prevPtr.setNextPtr(null); |
| } else if(nextPtr != null) { |
| headPtr = nextPtr; |
| nextPtr.setPrevPtr(null); |
| } |
| |
| return; |
| } |
| prev = e; |
| e = next; |
| } |
| } |
| |
| /** |
| * Removes and returns the entry associated with the specified |
| * key. |
| * |
| * @param key key of the entry to be deleted |
| * @return entry that was removed, or null if none found |
| */ |
| private Entry<K,V> removeEntryForKey(K key) { |
| int hash = hash(key); |
| int i = hashIndex(hash, entries.length); |
| Entry<K,V> prev = entries[i]; |
| Entry<K,V> e = prev; |
| |
| while (e != null) { |
| Entry<K,V> next = e.next; |
| if (e.hash == hash && isEqual(key, e.key)) { |
| size--; |
| if (prev == e) { |
| entries[i] = next; |
| } else { |
| prev.next = next; |
| } |
| |
| // Updating LRU |
| Entry<K,V> prevPtr = e.getPrevPtr(); |
| Entry<K,V> nextPtr = e.getNextPtr(); |
| if(prevPtr != null && nextPtr != null) { |
| prevPtr.setNextPtr(nextPtr); |
| nextPtr.setPrevPtr(prevPtr); |
| } else if(prevPtr != null) { |
| tailPtr = prevPtr; |
| prevPtr.setNextPtr(null); |
| } else if(nextPtr != null) { |
| headPtr = nextPtr; |
| nextPtr.setPrevPtr(null); |
| } |
| |
| return e; |
| } |
| prev = e; |
| e = next; |
| } |
| |
| return e; |
| } |
| |
| /** |
| * Adds a new entry with the specified key, value, hash code, and |
| * bucket index to the map. |
| * |
| * Also puts it in the bottom (most-recent) slot of the list and |
| * checks to see if we need to grow the array. |
| * |
| * @param hash hash value of key |
| * @param key the key |
| * @param value the value |
| * @param bucketIndex index into hash array to store this entry |
| * @return the amount of heap size used to store the new entry |
| */ |
| private long addEntry(int hash, K key, V value, int bucketIndex) { |
| Entry<K,V> e = entries[bucketIndex]; |
| Entry<K,V> newE = new Entry<K,V>(hash, key, value, e, tailPtr); |
| entries[bucketIndex] = newE; |
| // add as most recently used in lru |
| if (size == 0) { |
| headPtr = newE; |
| tailPtr = newE; |
| } else { |
| newE.setPrevPtr(tailPtr); |
| tailPtr.setNextPtr(newE); |
| tailPtr = newE; |
| } |
| // Grow table if we are past the threshold now |
| if (size++ >= threshold) { |
| growTable(2 * entries.length); |
| } |
| return newE.heapSize(); |
| } |
| |
| /** |
| * Clears all the entries in the map. Tracks the amount of memory being |
| * freed along the way and returns the total. |
| * |
| * Cleans up all references to allow old entries to be GC'd. |
| * |
| * @return total memory freed in bytes |
| */ |
| private long clearAll() { |
| Entry cur; |
| long freedMemory = 0; |
| for(int i=0; i<entries.length; i++) { |
| cur = entries[i]; |
| while(cur != null) { |
| freedMemory += cur.heapSize(); |
| cur = cur.next; |
| } |
| entries[i] = null; |
| } |
| headPtr = null; |
| tailPtr = null; |
| size = 0; |
| return freedMemory; |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * Recreates the entire contents of the hashmap into a new array |
| * with double the capacity. This method is called when the number of |
| * keys in the map reaches the current threshold. |
| * |
| * @param newCapacity the new size of the hash entries |
| */ |
| private void growTable(int newCapacity) { |
| Entry [] oldTable = entries; |
| int oldCapacity = oldTable.length; |
| |
| // Do not allow growing the table beyond the max capacity |
| if (oldCapacity == MAXIMUM_CAPACITY) { |
| threshold = Integer.MAX_VALUE; |
| return; |
| } |
| |
| // Determine how much additional space will be required to grow the array |
| long requiredSpace = (newCapacity - oldCapacity) * ClassSize.REFERENCE; |
| |
| // Verify/enforce we have sufficient memory to grow |
| checkAndFreeMemory(requiredSpace); |
| |
| Entry [] newTable = new Entry[newCapacity]; |
| |
| // Transfer existing entries to new hash table |
| for(int i=0; i < oldCapacity; i++) { |
| Entry<K,V> entry = oldTable[i]; |
| if(entry != null) { |
| // Set to null for GC |
| oldTable[i] = null; |
| do { |
| Entry<K,V> next = entry.next; |
| int idx = hashIndex(entry.hash, newCapacity); |
| entry.next = newTable[idx]; |
| newTable[idx] = entry; |
| entry = next; |
| } while(entry != null); |
| } |
| } |
| |
| entries = newTable; |
| threshold = (int)(newCapacity * loadFactor); |
| } |
| |
| /** |
| * Gets the hash code for the specified key. |
| * This implementation uses the additional hashing routine |
| * from JDK 1.4. |
| * |
| * @param key the key to get a hash value for |
| * @return the hash value |
| */ |
| private int hash(Object key) { |
| int h = key.hashCode(); |
| h += ~(h << 9); |
| h ^= (h >>> 14); |
| h += (h << 4); |
| h ^= (h >>> 10); |
| return h; |
| } |
| |
| /** |
| * Compares two objects for equality. Method uses equals method and |
| * assumes neither value is null. |
| * |
| * @param x the first value |
| * @param y the second value |
| * @return true if equal |
| */ |
| private boolean isEqual(Object x, Object y) { |
| return (x == y || x.equals(y)); |
| } |
| |
| /** |
| * Determines the index into the current hash table for the specified |
| * hashValue. |
| * |
| * @param hashValue the hash value |
| * @param length the current number of hash buckets |
| * @return the index of the current hash array to use |
| */ |
| private int hashIndex(int hashValue, int length) { |
| return hashValue & (length - 1); |
| } |
| |
| /** |
| * Calculates the capacity of the array backing the hash |
| * by normalizing capacity to a power of 2 and enforcing |
| * capacity limits. |
| * |
| * @param proposedCapacity the proposed capacity |
| * @return the normalized capacity |
| */ |
| private int calculateCapacity(int proposedCapacity) { |
| int newCapacity = 1; |
| if(proposedCapacity > MAXIMUM_CAPACITY) { |
| newCapacity = MAXIMUM_CAPACITY; |
| } else { |
| while(newCapacity < proposedCapacity) { |
| newCapacity <<= 1; |
| } |
| if(newCapacity > MAXIMUM_CAPACITY) { |
| newCapacity = MAXIMUM_CAPACITY; |
| } |
| } |
| return newCapacity; |
| } |
| |
| /** |
| * Calculates the threshold of the map given the capacity and load |
| * factor. Once the number of entries in the map grows to the |
| * threshold we will double the size of the array. |
| * |
| * @param capacity the size of the array |
| * @param factor the load factor of the hash |
| */ |
| private int calculateThreshold(int capacity, float factor) { |
| return (int)(capacity * factor); |
| } |
| |
| /** |
| * Set the initial heap usage of this class. Includes class variable |
| * overhead and the entry array. |
| */ |
| private void init() { |
| memFree -= OVERHEAD; |
| memFree -= (entries.length * ClassSize.REFERENCE); |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * Debugging function that returns a List sorted by access time. |
| * |
| * The order is oldest to newest (first in list is next to be evicted). |
| * |
| * @return Sorted list of entries |
| */ |
| public List<Entry<K,V>> entryLruList() { |
| List<Entry<K,V>> entryList = new ArrayList<Entry<K,V>>(); |
| Entry<K,V> entry = headPtr; |
| while(entry != null) { |
| entryList.add(entry); |
| entry = entry.getNextPtr(); |
| } |
| return entryList; |
| } |
| |
| /** |
| * Debugging function that returns a Set of all entries in the hash table. |
| * |
| * @return Set of entries in hash |
| */ |
| public Set<Entry<K,V>> entryTableSet() { |
| Set<Entry<K,V>> entrySet = new HashSet<Entry<K,V>>(); |
| Entry [] table = entries; // FindBugs IS2_INCONSISTENT_SYNC |
| for(int i=0;i<table.length;i++) { |
| for(Entry e = table[i]; e != null; e = e.next) { |
| entrySet.add(e); |
| } |
| } |
| return entrySet; |
| } |
| |
| /** |
| * Get the head of the linked list (least recently used). |
| * |
| * @return head of linked list |
| */ |
| public Entry getHeadPtr() { |
| return headPtr; |
| } |
| |
| /** |
| * Get the tail of the linked list (most recently used). |
| * |
| * @return tail of linked list |
| */ |
| public Entry getTailPtr() { |
| return tailPtr; |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * To best optimize this class, some of the methods that are part of a |
| * Map implementation are not supported. This is primarily related |
| * to being able to get Sets and Iterators of this map which require |
| * significant overhead and code complexity to support and are |
| * unnecessary for the requirements of this class. |
| */ |
| |
| /** |
| * Intentionally unimplemented. |
| */ |
| public Set<Map.Entry<K,V>> entrySet() { |
| throw new UnsupportedOperationException( |
| "entrySet() is intentionally unimplemented"); |
| } |
| |
| /** |
| * Intentionally unimplemented. |
| */ |
| public boolean equals(Object o) { |
| throw new UnsupportedOperationException( |
| "equals(Object) is intentionally unimplemented"); |
| } |
| |
| /** |
| * Intentionally unimplemented. |
| */ |
| public int hashCode() { |
| throw new UnsupportedOperationException( |
| "hashCode(Object) is intentionally unimplemented"); |
| } |
| |
| /** |
| * Intentionally unimplemented. |
| */ |
| public Set<K> keySet() { |
| throw new UnsupportedOperationException( |
| "keySet() is intentionally unimplemented"); |
| } |
| |
| /** |
| * Intentionally unimplemented. |
| */ |
| public void putAll(Map<? extends K, ? extends V> m) { |
| throw new UnsupportedOperationException( |
| "putAll() is intentionally unimplemented"); |
| } |
| |
| /** |
| * Intentionally unimplemented. |
| */ |
| public Collection<V> values() { |
| throw new UnsupportedOperationException( |
| "values() is intentionally unimplemented"); |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * Entry to store key/value mappings. |
| * <p> |
| * Contains previous and next pointers for the doubly linked-list which is |
| * used for LRU eviction. |
| * <p> |
| * Instantiations of this class are memory aware. Both the key and value |
| * classes used must also implement <code>HeapSize</code>. |
| */ |
| protected static class Entry<K extends HeapSize, V extends HeapSize> |
| implements Map.Entry<K,V>, HeapSize { |
| /** The baseline overhead memory usage of this class */ |
| static final int OVERHEAD = 1 * Bytes.SIZEOF_LONG + |
| 5 * ClassSize.REFERENCE + 2 * Bytes.SIZEOF_INT; |
| |
| /** The key */ |
| protected final K key; |
| /** The value */ |
| protected V value; |
| /** The hash value for this entries key */ |
| protected final int hash; |
| /** The next entry in the hash chain (for collisions) */ |
| protected Entry<K,V> next; |
| |
| /** The previous entry in the LRU list (towards LRU) */ |
| protected Entry<K,V> prevPtr; |
| /** The next entry in the LRU list (towards MRU) */ |
| protected Entry<K,V> nextPtr; |
| |
| /** The precomputed heap size of this entry */ |
| protected long heapSize; |
| |
| /** |
| * Create a new entry. |
| * |
| * @param h the hash value of the key |
| * @param k the key |
| * @param v the value |
| * @param nextChainPtr the next entry in the hash chain, null if none |
| * @param prevLruPtr the previous entry in the LRU |
| */ |
| Entry(int h, K k, V v, Entry<K,V> nextChainPtr, Entry<K,V> prevLruPtr) { |
| value = v; |
| next = nextChainPtr; |
| key = k; |
| hash = h; |
| prevPtr = prevLruPtr; |
| nextPtr = null; |
| // Pre-compute heap size |
| heapSize = OVERHEAD + k.heapSize() + v.heapSize(); |
| } |
| |
| /** |
| * Get the key of this entry. |
| * |
| * @return the key associated with this entry |
| */ |
| public K getKey() { |
| return key; |
| } |
| |
| /** |
| * Get the value of this entry. |
| * |
| * @return the value currently associated with this entry |
| */ |
| public V getValue() { |
| return value; |
| } |
| |
| /** |
| * Set the value of this entry. |
| * |
| * It is not recommended to use this method when changing the value. |
| * Rather, using <code>replaceValue</code> will return the difference |
| * in heap usage between the previous and current values. |
| * |
| * @param newValue the new value to associate with this entry |
| * @return the value previously associated with this entry |
| */ |
| public V setValue(V newValue) { |
| V oldValue = value; |
| value = newValue; |
| return oldValue; |
| } |
| |
| /** |
| * Replace the value of this entry. |
| * |
| * Computes and returns the difference in heap size when changing |
| * the value associated with this entry. |
| * |
| * @param newValue the new value to associate with this entry |
| * @return the change in heap usage of this entry in bytes |
| */ |
| protected long replaceValue(V newValue) { |
| long sizeDiff = newValue.heapSize() - value.heapSize(); |
| value = newValue; |
| heapSize += sizeDiff; |
| return sizeDiff; |
| } |
| |
| /** |
| * Returns true is the specified entry has the same key and the |
| * same value as this entry. |
| * |
| * @param o entry to test against current |
| * @return true is entries have equal key and value, false if no |
| */ |
| public boolean equals(Object o) { |
| if (!(o instanceof Map.Entry)) |
| return false; |
| Map.Entry e = (Map.Entry)o; |
| Object k1 = getKey(); |
| Object k2 = e.getKey(); |
| if (k1 == k2 || (k1 != null && k1.equals(k2))) { |
| Object v1 = getValue(); |
| Object v2 = e.getValue(); |
| if (v1 == v2 || (v1 != null && v1.equals(v2))) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the hash code of the entry by xor'ing the hash values |
| * of the key and value of this entry. |
| * |
| * @return hash value of this entry |
| */ |
| public int hashCode() { |
| return (key.hashCode() ^ value.hashCode()); |
| } |
| |
| /** |
| * Returns String representation of the entry in form "key=value" |
| * |
| * @return string value of entry |
| */ |
| public String toString() { |
| return getKey() + "=" + getValue(); |
| } |
| |
| //------------------------------------------------------------------------ |
| /** |
| * Sets the previous pointer for the entry in the LRU. |
| * @param prevPtr previous entry |
| */ |
| protected void setPrevPtr(Entry<K,V> prevPtr){ |
| this.prevPtr = prevPtr; |
| } |
| |
| /** |
| * Returns the previous pointer for the entry in the LRU. |
| * @return previous entry |
| */ |
| protected Entry<K,V> getPrevPtr(){ |
| return prevPtr; |
| } |
| |
| /** |
| * Sets the next pointer for the entry in the LRU. |
| * @param nextPtr next entry |
| */ |
| protected void setNextPtr(Entry<K,V> nextPtr){ |
| this.nextPtr = nextPtr; |
| } |
| |
| /** |
| * Returns the next pointer for the entry in teh LRU. |
| * @return next entry |
| */ |
| protected Entry<K,V> getNextPtr(){ |
| return nextPtr; |
| } |
| |
| /** |
| * Returns the pre-computed and "deep" size of the Entry |
| * @return size of the entry in bytes |
| */ |
| public long heapSize() { |
| return heapSize; |
| } |
| } |
| } |
| |
| |