001/** 002 * 003 * Copyright 2003-2005 Jive Software, 2014-2023 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 * @param <K> the type of the keys of this cache. 032 * @param <V> the type of the values this cache caches. 033 */ 034public class LruCache<K, V> extends LinkedHashMap<K, V> implements Cache<K, V> { 035 036 /** 037 * 038 */ 039 private static final long serialVersionUID = -4980809402073634607L; 040 041 /** 042 * The default initial value for the used data structures. 043 */ 044 private static final int DEFAULT_INITIAL_SIZE = 50; 045 046 /** 047 * Maximum number of items the cache will hold. 048 */ 049 private int maxCacheSize; 050 051 /** 052 * Maintain the number of cache hits and misses. A cache hit occurs every 053 * time the get method is called and the cache contains the requested 054 * object. A cache miss represents the opposite occurrence. 055 * <p> 056 * Keeping track of cache hits and misses lets one measure how efficient 057 * the cache is; the higher the percentage of hits, the more efficient. 058 */ 059 private final AtomicLong cacheHits = new AtomicLong(); 060 private final AtomicLong cacheMisses = new AtomicLong(); 061 062 /** 063 * Create a new cache and specify the maximum size of for the cache in 064 * bytes, and the maximum lifetime of objects. 065 * 066 * @param maxSize the maximum number of objects the cache will hold. -1 067 * means the cache has no max size. 068 */ 069 public LruCache(int maxSize) { 070 super(maxSize < DEFAULT_INITIAL_SIZE ? maxSize : DEFAULT_INITIAL_SIZE, 071 0.75f, true); 072 if (maxSize == 0) { 073 throw new IllegalArgumentException("Max cache size cannot be 0."); 074 } 075 this.maxCacheSize = maxSize; 076 } 077 078 @Override 079 protected final boolean removeEldestEntry(Map.Entry<K, V> eldest) { 080 return size() > maxCacheSize; 081 } 082 083 @Override 084 public final synchronized V put(K key, V value) { 085 return super.put(key, value); 086 } 087 088 @Override 089 public final V lookup(K key) { 090 return get(key); 091 } 092 093 @Override 094 public final V get(Object key) { 095 V cacheObject; 096 synchronized (this) { 097 cacheObject = super.get(key); 098 } 099 if (cacheObject == null) { 100 // The object didn't exist in cache, so increment cache misses. 101 cacheMisses.incrementAndGet(); 102 return null; 103 } 104 105 // The object exists in cache, so increment cache hits. Also, increment 106 // the object's read count. 107 cacheHits.incrementAndGet(); 108 109 return cacheObject; 110 } 111 112 @Override 113 public final synchronized V remove(Object key) { 114 return super.remove(key); 115 } 116 117 @Override 118 public final void clear() { 119 synchronized (this) { 120 super.clear(); 121 } 122 cacheHits.set(0); 123 cacheMisses.set(0); 124 } 125 126 @Override 127 public final synchronized int size() { 128 return super.size(); 129 } 130 131 @Override 132 public final synchronized boolean isEmpty() { 133 return super.isEmpty(); 134 } 135 136 @Override 137 public final synchronized Collection<V> values() { 138 return super.values(); 139 } 140 141 @Override 142 public final synchronized boolean containsKey(Object key) { 143 return super.containsKey(key); 144 } 145 146 @Override 147 public final synchronized void putAll(Map<? extends K, ? extends V> m) { 148 super.putAll(m); 149 } 150 151 @Override 152 public final synchronized boolean containsValue(Object value) { 153 return super.containsValue(value); 154 } 155 156 @Override 157 public final synchronized Set<java.util.Map.Entry<K, V>> entrySet() { 158 return super.entrySet(); 159 } 160 161 @Override 162 public final synchronized Set<K> keySet() { 163 return super.keySet(); 164 } 165 166 /** 167 * Get the number of cache hits. 168 * 169 * @return the number of cache hits. 170 */ 171 public final long getCacheHits() { 172 return cacheHits.longValue(); 173 } 174 175 /** 176 * Get the number of cache misses. 177 * 178 * @return the number of cache misses. 179 */ 180 public final long getCacheMisses() { 181 return cacheMisses.longValue(); 182 } 183 184 @Override 185 public final int getMaxCacheSize() { 186 return maxCacheSize; 187 } 188 189 @Override 190 public final void setMaxCacheSize(int maxCacheSize) { 191 this.maxCacheSize = maxCacheSize; 192 } 193}