Java中内置有一个String.intern方法,对于重复使用的内容相同而内存地址不同的String对象,调用intern方法可以节省内存空间。如果将String作为HashMap的键来使用,intern可以提高性能。
那么今天我来向大家介绍一个针对Object的intern。Java没有提供这个方法,所以我实现了它。
package pers.laserpen.util.java;
import java.util.Map.Entry;
import java.util.WeakHashMap;
import java.util.function.UnaryOperator;
/**
* 对象唯一引用。<br/>
*
* @author Laserpen
*/
public class WeakIntern<TYPE> {
private final WeakHashMap<TYPE, TYPE> m_normalObjectToInternObject = new WeakHashMap<>();
private final WeakHashMap<TYPE, Object> m_internObjectStorage = new WeakHashMap<>();
private final EquivalenceJudgment<TYPE> m_comparator;
private final UnaryOperator<TYPE> m_internCreator;
public WeakIntern(EquivalenceJudgment<TYPE> comparator, UnaryOperator<TYPE> internCreator) {
m_comparator = comparator;
m_internCreator = internCreator;
}
/**
* 查找或创建一个intern对象。<br/>
*
* @param rawObject
* @return
*/
public final TYPE intern(final TYPE rawObject) {
// 空值是唯一的,也不需要加线程锁
if (rawObject == null) {
return null;
}
synchronized (m_normalObjectToInternObject) {
{// 尝试确定输入是不是本身就是intern。我认为找到intern后,程序中会一直使用intern,而源数据很可能被抛弃。所以输入是intern的概率较大。
if (m_internObjectStorage.containsKey(rawObject)) {
return rawObject;
}
}
{// 尝试查找可能的旧intern对象。这个源对象有可能是之前处理过的。
TYPE internObject = m_normalObjectToInternObject.get(rawObject);
// 已经有一个intern对象
if (internObject != null) {
return internObject;
}
}
// 现在可以确定输入不可能是intern,也没有历史记录。随后将进行复杂的计算。
// 尝试在intern仓库中查找可用的旧intern对象。
for (Entry<TYPE, Object> objectInInternSet : m_internObjectStorage.entrySet()) {
TYPE internObject = objectInInternSet.getKey();
boolean comp = compare(rawObject, internObject);
// 相同文件共享intern对象
if (comp) {
// 存入映射清单,加速下一次的匹配
m_normalObjectToInternObject.put(rawObject, internObject);
return internObject;
}
}
{ // 创建新intern对象,并放入intern仓库。
TYPE internObject = m_internCreator.apply(rawObject);
m_internObjectStorage.put(internObject, null);
// intern创建函数有可能会返回源对象。不能把用于intern的对象放入映射表
if (rawObject != internObject) {
// 存入映射清单,加速下一次的匹配
m_normalObjectToInternObject.put(rawObject, internObject);
}
return internObject;
}
}
}
private boolean compare(final TYPE a, final TYPE b) {
if (a == b) {
return true;
}
if (a == null) {
return false;
}
if (b == null) {
return false;
}
return m_comparator.areEquivalent(a, b);
}
@FunctionalInterface
public static interface EquivalenceJudgment<TYPE> {
public boolean areEquivalent(TYPE o1, TYPE o2);
}
}
为什么我创建了一个接口EquivalenceJudgment,而不使用Object.equals方法?
因为有些对象的equals方法不完美。比如File.equals其实是对字符串的比较,指向同一文件的File对象如果一个是绝对路径,一个是相对路径,那么File.equals就会返回false。增加一个定制的EquivalenceJudgment接口是为了重新定义两个对象相等的条件。当然,对于大多数Java自带的类型,原本的equals还是可以使用的。
internCreator的存在则是为了提供创建intern对象的两种选择。你可以选择使用第一个对象当作intern对象,也可以复制一个。
现在贴一个对File的实现:
package pers.laserpen.util.file;
import java.io.File;
import com.sun.jna.Platform;
import pers.laserpen.util.java.WeakIntern;
/**
* File对象唯一引用,用于文件对比和文件操作的线程锁。如果同一进程有两个线程需要文件锁,那么需要先加线程锁。
*
* @author Laserpen
*/
public final class WeakFileIntern extends WeakIntern<File> {
public WeakFileIntern() {
super((a, b) -> 0 == compareFile(a, b), file -> new File(StaticFileUtils.tryGetCanonicalPath(file)));
}
private static int compareFile(final File a, final File b) {
if (a == b) {
return 0;
}
if (a == null) {
return -1;
}
if (b == null) {
return 1;
}
String fileAString = StaticFileUtils.tryGetCanonicalPath(a);
int fileALength = fileAString.length();
String fileBString = StaticFileUtils.tryGetCanonicalPath(b);
int fileBLength = fileBString.length();
if (Platform.isWindows() || Platform.isWindowsCE()) {
fileAString = fileAString.toLowerCase();
fileBString = fileBString.toLowerCase();
}
int minLength = Math.min(fileALength, fileBLength);
for (int i = 0; i < minLength; ++i) {
char ca = fileAString.charAt(i);
char cb = fileBString.charAt(i);
int delta = ca - cb;
if (delta != 0) {
return delta;
}
}
return fileALength - fileBLength;
}
}
这个已经不是核心代码,关联代码就不继续扩展了,作为参考,不能运行。