001/** 002 * 003 * Copyright 2003-2005 Jive Software, 2014-2018 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jxmpp.util.cache; 018 019import java.util.Collection; 020import java.util.LinkedHashMap; 021import java.util.Map; 022import java.util.Set; 023import java.util.concurrent.atomic.AtomicLong; 024 025/** 026 * A specialized Map that is size-limited (using an LRU algorithm). The Map is 027 * thread-safe. 028 * 029 * @author Matt Tucker 030 * @author Florian Schmaus 031 */ 032public class LruCache<K, V> extends LinkedHashMap<K, V> implements Cache<K, V> { 033 034 /** 035 * 036 */ 037 private static final long serialVersionUID = -4980809402073634607L; 038 039 /** 040 * The default initial value for the used data structures. 041 */ 042 private static final int DEFAULT_INITIAL_SIZE = 50; 043 044 /** 045 * Maximum number of items the cache will hold. 046 */ 047 private int maxCacheSize; 048 049 /** 050 * Maintain the number of cache hits and misses. A cache hit occurs every 051 * time the get method is called and the cache contains the requested 052 * object. A cache miss represents the opposite occurrence. 053 * <p> 054 * Keeping track of cache hits and misses lets one measure how efficient 055 * the cache is; the higher the percentage of hits, the more efficient. 056 */ 057 private final AtomicLong cacheHits = new AtomicLong(); 058 private final AtomicLong cacheMisses = new AtomicLong(); 059 060 /** 061 * Create a new cache and specify the maximum size of for the cache in 062 * bytes, and the maximum lifetime of objects. 063 * 064 * @param maxSize the maximum number of objects the cache will hold. -1 065 * means the cache has no max size. 066 */ 067 public LruCache(int maxSize) { 068 super(maxSize < DEFAULT_INITIAL_SIZE ? maxSize : DEFAULT_INITIAL_SIZE, 069 0.75f, true); 070 if (maxSize == 0) { 071 throw new IllegalArgumentException("Max cache size cannot be 0."); 072 } 073 this.maxCacheSize = maxSize; 074 } 075 076 @Override 077 protected final boolean removeEldestEntry(Map.Entry<K, V> eldest) { 078 return size() > maxCacheSize; 079 } 080 081 @Override 082 public final synchronized V put(K key, V value) { 083 return super.put(key, value); 084 } 085 086 @Override 087 public final V lookup(K key) { 088 return get(key); 089 } 090 091 @Override 092 public final V get(Object key) { 093 V cacheObject; 094 synchronized (this) { 095 cacheObject = super.get(key); 096 } 097 if (cacheObject == null) { 098 // The object didn't exist in cache, so increment cache misses. 099 cacheMisses.incrementAndGet(); 100 return null; 101 } 102 103 // The object exists in cache, so increment cache hits. Also, increment 104 // the object's read count. 105 cacheHits.incrementAndGet(); 106 107 return cacheObject; 108 } 109 110 @Override 111 public final synchronized V remove(Object key) { 112 return super.remove(key); 113 } 114 115 @Override 116 public final void clear() { 117 synchronized (this) { 118 super.clear(); 119 } 120 cacheHits.set(0); 121 cacheMisses.set(0); 122 } 123 124 @Override 125 public final synchronized int size() { 126 return super.size(); 127 } 128 129 @Override 130 public final synchronized boolean isEmpty() { 131 return super.isEmpty(); 132 } 133 134 @Override 135 public final synchronized Collection<V> values() { 136 return super.values(); 137 } 138 139 @Override 140 public final synchronized boolean containsKey(Object key) { 141 return super.containsKey(key); 142 } 143 144 @Override 145 public final synchronized void putAll(Map<? extends K, ? extends V> m) { 146 super.putAll(m); 147 } 148 149 @Override 150 public final synchronized boolean containsValue(Object value) { 151 return super.containsValue(value); 152 } 153 154 @Override 155 public final synchronized Set<java.util.Map.Entry<K, V>> entrySet() { 156 return super.entrySet(); 157 } 158 159 @Override 160 public final synchronized Set<K> keySet() { 161 return super.keySet(); 162 } 163 164 /** 165 * Get the number of cache hits. 166 * 167 * @return the number of cache hits. 168 */ 169 public final long getCacheHits() { 170 return cacheHits.longValue(); 171 } 172 173 /** 174 * Get the number of cache misses. 175 * 176 * @return the number of cache misses. 177 */ 178 public final long getCacheMisses() { 179 return cacheMisses.longValue(); 180 } 181 182 @Override 183 public final int getMaxCacheSize() { 184 return maxCacheSize; 185 } 186 187 @Override 188 public final void setMaxCacheSize(int maxCacheSize) { 189 this.maxCacheSize = maxCacheSize; 190 } 191}