今天研究Glide源码时看到了DataLoadProviderRegistry中的取值操作。先让大家看下该代码:
public class DataLoadProviderRegistry {
private static final MultiClassKey GET_KEY = new MultiClassKey();
private final Map<MultiClassKey, DataLoadProvider<?, ?>> providers =
new HashMap<MultiClassKey, DataLoadProvider<?, ?>>();
public <T, Z> void register(Class<T> dataClass, Class<Z> resourceClass, DataLoadProvider<T, Z> provider) {
providers.put(new MultiClassKey(dataClass, resourceClass), provider);
}
@SuppressWarnings("unchecked")
public <T, Z> DataLoadProvider<T, Z> get(Class<T> dataClass, Class<Z> resourceClass) {
DataLoadProvider<?, ?> result;
synchronized (GET_KEY) {
GET_KEY.set(dataClass, resourceClass);
result = providers.get(GET_KEY);
}
if (result == null) {
result = EmptyDataLoadProvider.get();
}
return (DataLoadProvider<T, Z>) result;
}
}
可以看出,register中的key是通过new得到的,但是在get方法中取值却是通过GET_KEY这个常量获取到的。通常我们都会认为,因为put时没有用过该key,返回的一定是null,但是实际上的的确确获取到了之前的值,这是为什么?
先不解释原因,先看下是不是MultiClassKey这个类的原因呢?
public class MultiClassKey {
private Class<?> first;
private Class<?> second;
public MultiClassKey() {
// leave them null
}
public MultiClassKey(Class<?> first, Class<?> second) {
set(first, second);
}
public void set(Class<?> first, Class<?> second) {
this.first = first;
this.second = second;
}
@Override
public String toString() {
return "MultiClassKey{"
+ "first=" + first
+ ", second=" + second
+ '}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MultiClassKey that = (MultiClassKey) o;
if (!first.equals(that.first)) {
return false;
}
if (!second.equals(that.second)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = first.hashCode();
result = 31 * result + second.hashCode();
return result;
}
}
可以看出其重写了equals和hashCode,用来保证“它”就是我。HashMap是不是通过判断equals和hashCode来找到“我”,然后映射到对应的value值?
答案是肯定的,了解HashMap原理的人都知道,其取值过程正是如此:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
这里着重要看两点,即HashMap取值原理:
// 1,通过(n-1)&hash得出key在HashMap中的索引位置
Node<K,V> first = tab[(n - 1) & hash]
// 2,通过判断节点的hash值以及equals的异同来判断是否找到对应key
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
这就是是文章开头的部分为什么可以通过GET_KEY来获取到之前的取值了,实际上正是HashMap通过key值的hash值和equals的异同来找到对应value。
当然通过该两步也不一定就可以找到指定value,对于HashMap的put出现冲突时就会轮询判断node的next的hashCode和equals的异同来决定是覆盖还是存到next当中,所以get也就是同样道理,不断轮询判断next的hashCode和equals方法的异同来判断是否已找到指定value:
if ((e = first.next) != null) {
// 如果链表长度超过8个,则通过红黑树的结构存储数据,提高数据的访问速度
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
下边是题外话
原因以及原理都已经分析过了。大家有没有想过为什么文章开头的DataLoadProviderRegistry这个类非要用这种操作呢?通常在get中直接把key传过来不就完事了吗?
为什么?
好了,大家再看下DataLoadProviderRegistry的get传参以及MultiClassKey:
public <T, Z> DataLoadProvider<T, Z> get(Class<T> dataClass, Class<Z> resourceClass){...}
public class MultiClassKey {
private Class<?> first;
private Class<?> second;
...
}
我想表达什么意思呢?
问题一:如何保证多个对象映射一个value值?
通常我们的做法是不是有几个对象就写几个映射表,然后都映射一个value值呢?这样做有什么问题呢?
问题在于增加了代码的编写复杂性,难以阅读和维护,那该如何解决?
答案是将多个对象封装成一个对象,就是这么easy。
问题二:get时为什么要通过常量key取值?
对于源码这种特殊情况,我们通常的做法一般就是遍历HashMap的key,然后通过判断key中每个属性是否和get传参中是否一致来查找指定key(包括各属性值以及组合后的hashCode),但是源码只需要一个GET_KEY便解决的问题。好处在于减小了处理时的时间复杂度,以及更精简的编码,利于维护。
好了,通过该源码有没有给大家一些启发以及今后的一些开发建议呢?
网友评论