软引用类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会在引发“内存不足”异常前先回收这些对象的内存。软引用可用来实现内存敏感的高速缓存。
Java自带了一个 WeakHashMap 类用以实现弱引用,自带一个 SoftCache 类用以实现软引用缓存,以下给出自定义的软引用类的源码。
package com.ffcs.ods.common.util;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
/**
* @版权:福富软件 版权所有 (c)
* @文件:com.ffcs.ods.common.util.SoftHashMap.java
* @author: g.yanjj
* @see: Map, HashMap, WeakHashMap, SoftCache
* @创建日期:
* @功能说明:具有软引用特性的哈希表,可用于存放缓存数据
* @修改记录:
*/
public class SoftHashMap<K,V> implements Map<K,V> {
/**
* 用来存放软引用的内部Map
*/
private final Map<K, SoftReferValue<K,V>> hash = new HashMap<K, SoftReferValue<K,V>>();
/**
* 用来存放已被清除的软引用对象
*/
private ReferenceQueue<V> queue = new ReferenceQueue<V>();
/**
* 需要保留最近使用的缓存的数量,为0则代表不用特意保留缓存
*/
private final int HARD_SIZE;
/**
* 用来保留最近使用的缓存的FIFO链表,将最近使用过的缓存存入链首
*/
private final LinkedList<V> hardCache = new LinkedList<V>();
/**
* 具有软引用特性的哈希表
*/
public SoftHashMap() {
HARD_SIZE = 0;
}
/**
* 具有软引用特性的哈希表
*
* @param keepSize 指定最多需要保留的最近使用过的缓存数量
*/
public SoftHashMap(int keepSize) {
HARD_SIZE = keepSize;
}
/**
* 清除所有的键值对,执行后哈希表为空。
*/
@Override
public void clear() {
hardCache.clear();
clearReferQueue();
hash.clear();
}
/**
* 判断哈希表是否包含指定的键
*
* @param key 要判断的键对象,键对象为null时一定返回false。
* @return <tt>true</tt> 包含指定键,<tt>false</tt> 不包含指定键。
*/
@Override
public boolean containsKey(Object key) {
if (null != key){
clearReferQueue();
return hash.containsKey(key);
} else {
return false;
}
}
/**
* 判断哈希表是否包含有指定的值
*
* @param value 要判断的值对象,值对象为null时一定返回false。
* @return <tt>true</tt> 包含指定值,<tt>false</tt> 不包含指定值。
*/
@Override
public boolean containsValue(Object value) {
if (null != value) {
clearReferQueue();
for (SoftReferValue<K,V> srv : hash.values()) {
if (value.equals(srv.get())) {
return true;
}
}
}
return false;
}
/**
* SoftHashMap类并没有使用内部集,因此此方法会抛出一个异常
*
* @return 不会返回任何对象,抛出异常
* @exception UnsupportedOperationException
*/
@Override
public Set<Map.Entry<K,V>> entrySet() {
//没有内部集合类
throw new UnsupportedOperationException();
}
/**
* 根据键对象,获取值对象。
*
* <p>由于值对象可能会被回收,此方法只会返回键存在,并且未被回收的值对象。
*
* <p>如果在构造函数中指定了“缓存保留数”,则最近获取的“缓存保留数”个数的值对象将不会被回收。
*
* @param key 要用于取值的键对象
* @return 返回对应的未被回收的值对象,如果键不存在或值对象已被回收,则返回{@code null}
*/
@Override
public V get(Object key) {
V result = null;
//根据键获取值的引用
SoftReferValue<K,V> valueRef = hash.get(key);
if (null != valueRef) {
//软引用所指向的对象有可能已被回收,因此需要判断目标对象是否为空
result = valueRef.get();
if (null == result) {
//如果目标对象已被回收,则从哈希表中移除这个无效键值对
hash.remove(key);
clearReferQueue();
} else {
//如果需要保留最近使用的缓存,则将目标对象推入强引用链表
if (HARD_SIZE > 0) {
hardCache.addFirst(result);
if (hardCache.size() > HARD_SIZE) {
hardCache.removeLast();
}
}
}
}
return result;
}
/**
* 判断哈希表是否是空的
*
* @return <tt>true</tt>哈希表没有任何键值对,<tt>false</tt>哈希表至少有一个键值对。
*/
@Override
public boolean isEmpty() {
return size() == 0;
}
/**
* 返回哈希表的键对象的集合
*/
@Override
public Set<K> keySet() {
return hash.keySet();
}
/**
* 将键值对添加到哈希表
*
* @param key 键对象类似一个索引,可用于查找值对象
* @param value 值对象是保存实际数据的对象,可通过键对象查找
* @return 如果该键对象之前已在哈希表中存在,则返回原有的值对象。
* 如果返回{@code null},可能是该键对象第一次添加到哈希表,或原先存放的值对象为null,或原先存放的值对象已被回收。
*/
@Override
public V put(K key, V value) {
clearReferQueue();
SoftReferValue<K,V> srv = new SoftReferValue<K,V>(value, key, queue);
srv = hash.put(key, srv);
if (null == srv){
return null;
} else {
return srv.get();
}
}
/**
* 将指定Map中的键值对,批量添加到哈希表
*
* @param m 要添加到哈希表的源表
*/
@Override
public void putAll(Map<? extends K, ? extends V> m) {
for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<? extends K, ? extends V> e = i.next();
hash.put(e.getKey(), new SoftReferValue<K,V>(e.getValue(), e.getKey(), queue));
}
}
/**
* 根据指定键,从哈希表移除对应的键值对
*
* @param key 要移除的键
* @return 返回被移除的值对象,如果哈希表不存在指定键则返回{@code null}
*/
@Override
public V remove(Object key) {
clearReferQueue();
SoftReferValue<K,V> srv = hash.remove(key);
if (null == srv){
return null;
} else {
return srv.get();
}
}
/**
* 返回哈希表有效的键值对数量,在返回之前,已被回收的值对象将被移除
*
* @return 哈希表有效的键值对数量
*/
@Override
public int size() {
clearReferQueue();
return hash.size();
}
/**
* SoftHashMap类不支持返回值对象集合,此方法会抛出一个异常
*
* @return 不会返回任何对象,抛出异常
* @exception UnsupportedOperationException
*/
@Override
public Collection<V> values() {
//不支持获取软引用集合
throw new UnsupportedOperationException();
}
/**
* 继承的软引用内部类,加入key属性是为了方便从值对象查找对应的键
*/
private static class SoftReferValue<K,V> extends SoftReference<V> {
private final K key;
private SoftReferValue(V value, K key, ReferenceQueue<V> queue) {
super(value, queue);
this.key = key;
}
}
/**
* 清空引用队列,清除已被回收的软引用对象
*/
private void clearReferQueue() {
SoftReferValue<K,V> srv;
while ((srv = (SoftReferValue<K,V>) queue.poll()) != null) {
hash.remove(srv.key);
}
}
}
使用示例:
public class SysManagerImpl extends BaseManager implements ServletContextAware {
private ServletContext servletContext;
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
private List getPubDomain(String domainId) {
final String contextName = "SYSCACHE.PUB_DOMAIN";
final String cacheName = domainId;
List result = null;
//从上下文中获取缓存数据
Map<String, List> cacheData = (Map<String, List>) servletContext.getAttribute(contextName);
if (null == cacheData){
cacheData = new SoftHashMap<String, List>();
} else {
if (cacheData.containsKey(cacheName)) {
result = cacheData.get(cacheName);
}
}
//如果上下文中没有指定缓存,则重新从数据库获取
if (null == result) {
result = getPubDomainForSQL(domainId);
cacheData.put(cacheName, result);
servletContext.setAttribute(contextName, cacheData);
}
//返回指定数据
return result;
}
}
查询所有缓存示例:
public List querySysCache() {
List result = new ArrayList();
Enumeration<E> e = servletContext.getAttributeNames();
while (e.hasMoreElements()) {
String domainName = e.nextElement().toString();
if (domainName.startsWith("SYSCACHE.")) {
Map cacheData = (Map) servletContext.getAttribute(domainName);
for (Object cacheName : cacheData.keySet()) {
String[] data = new String[2];
data[0] = domainName.substring(9);
data[1] = cacheName.toString();
}
}
}
return result;
}