注:源码系列文章主要是对某付费专栏的总结记录。如有侵权,请联系删除。
在日常工作中,我们经常会使用一些第三方的 API 来简化我们的工作,Guava 就是其中的一种,Guava 是 Google 开源的技术框架,使用率高,社区活跃度也高。
源码地址:https://github.com/google/guava
1 运用工厂模式进行初始化
在集合类初始化方面,Guava 比 Java 原生的 API 更加好用,还发明了很多新的功能,比如说在 JDK 7 之前,我们新建集合类时,声明和初始化都必须写上泛型类型,像这样:List<泛型> list = new ArrayList<泛型>();
,JDK 7 之后有所改变,我们只需要在声明处写上泛型说明:List<泛型> list = new Arraylist<>();
。
Guava 提供了更加方便的使用姿势,采用了工厂模式,把集合创建的逻辑交给了工厂,开发者无需关注工厂底层是如何实现的,只需要关心工厂能生产什么,于是代码变成了这样:List<泛型> list = Lists.newArrayList();
,Lists 就是 Guava 提供出来的,方便操作 List 的工具类。
这种写法其实就是一种简单的工厂模式,只需要简单的定义好工厂的入场和出参,就能对外隐藏其内部的创建逻辑,提供了更加方便的使用体验。
当然除了 Lists,Guava 还提供了很多,如 Maps、Sets 等等。
2 Lists
2.1 初始化
Lists 最大的功能就是帮助我们进行 list 的初始化,比如上面说的 newArrayList:
// 底层帮助我们写好了泛型,E 代表泛型,表示当前返回的泛型类型和声明的一致即可,
// 在编译的时候,会把泛型 E 转化成我们声明的 String
public static <E> ArrayList<E> newArrayList() {
return new ArrayList<>();
}
如果你清楚 List 的大小,也可以这样做:
// 可以预估 list 的大小为 20
Lists.newArrayListWithCapacity(20);
/ 不太肯定 list 大小是多少,但期望大小是 20 上下
Lists.newArrayListWithExpectedSize(20);
newArrayListWithCapacity(20)
方法内部实现是 new ArrayList<>(20)
,而 newArrayListWithExpectedSize(20)
方法内部实现是对 List 大小有一个计算公式:Ints.saturatedCast(5L + arraySize + (arraySize / 10))
,arraySize 表示传进来的值,公式简化下就是 5 + 11/10 * arraySize
,因为这个方法表示期望的大小,所以这里取的约是期望值的十分之十一,比传进来的值约大十分之一,所以根据 20 最终计算出来的值是 27。
从 Lists 对 List 初始化进行包装的底层源码来看,底层源码非常简单的,但我们还是愿意使用这种方式的包装,主要是因为这种工厂模式的包装,使我们的使用姿势更加优雅,使用起来更加方便。
2.2 分组和反转排序
除了初始化之外,Lists 还提供了两个比较实用的功能,分组和反转排序功能,示例:
List<String> list = new ArrayList<String>() {{
add("10");
add("20");
add("30");
add("40");
add("50");
}};
System.out.println("反转之前:" + JSON.toJSONString(list));
list = Lists.reverse(list);
System.out.println("反转之后:" + JSON.toJSONString(list));
执行结果:
反转之前:["10","20","30","40","50"]
反转之后:["50","40","30","20","10"]
reverse 方法底层实现非常巧妙,底层覆写了 List 原生的 get(index) 方法,会把传进来的 index 进行 (size - 1) - index
的计算,使计算得到的索引位置和 index 位置正好相反,这样我们在 get 时,数组索引位置的 index 已经是相反的位置了,达到了反转排序的效果,其实底层并没有进行反转排序,只是在计算相反的索引位置,通过计算相反的索引位置这样简单的设计,得到了反转排序的效果,非常精妙。
在工作中,我们需要把一个 list 进行切分,然后再把每一份丢给线程池去运行,左后把每份运行的结果汇总,Lists 工具类就提供了一个对 list 进行切分分组的方法,示例:
List<String> list = new ArrayList<String>() {{
add("10");
add("20");
add("30");
add("40");
add("50");
}};
System.out.println("分组之前:" + JSON.toJSONString(list));
List<List<String>> list2 = Lists.partition(list, 3);
System.out.println("分组之后:" + JSON.toJSONString(list2));
执行结果:
分组之前:["10","20","30","40","50"]
分组之后:[["10","20","30"],["40","50"]]
partition 方法的第二个参数的意思是,你想让分组后的 List 包含几个元素,这个方法底层实现其实就是 subList 方法。
有一点需要注意的是这两个方法返回的 List 并不是 ArrayList,是自定义的 List,所以对于 ArrayList 的有些功能可能并不支持,使用的时候最好能看下源码,看看底层有无支持。
2.3 小结
Lists 上述的方法大大的方便了我们进行开发,简化了使用姿势,但其内部实现却分厂简单巧妙,比如 reverse 方法可以输出相反排序的 List,但底层并没有实现排序,只是计算了索引位置的相反值而已,这点值的我们学习。
3 Maps
3.1 初始化
Maps 也有初始化 Map 的各种方法,原理和 Lists 类似,使用演示:
Maps.newHashMap();
Maps.newLinkedHashMap();
// 这里 Map 的初始化大小公式和 HashSet 初始化公式类似,HashSet 入参集合初始化 HashMap 时,
// 经典的计算初始大小的公式:取最大值(期望的值/0.75 + 1, 默认值 16)
// newHashMapWithExpectedSize 方法底层也是这么算的初始化大小
Maps.newHashMapWithExpectedSize(20);
Maps.newHashMapWithExpectedSize(int expectedSize)
源码如下:
public static <K, V> HashMap<K, V> newHashMapWithExpectedSize(int expectedSize) {
return new HashMap<>(capacity(expectedSize));
}
static int capacity(int expectedSize) {
if (expectedSize < 3) {
checkNonnegative(expectedSize, "expectedSize");
return expectedSize + 1;
}
if (expectedSize < Ints.MAX_POWER_OF_TWO) {
// This is the calculation used in JDK8 to resize when a putAll
// happens; it seems to be the most conservative calculation we
// can make. 0.75 is the default load factor.
return (int) ((float) expectedSize / 0.75F + 1.0F);
}
return Integer.MAX_VALUE; // any large value
}
3.2 difference
Maps 提供了一个特别有趣且使用的方法:difference,此方法的目的是比较两个 Map 的差异,入参就是两个 Map,比较之后能返回四种差异:
- 左边 Map 独有的 key;
- 右边 Map 独有的 key;
- 左右 Map 都有的 key,并且 value 相等;
- 左右 Map 都有的 key,但是 value’ 不相等。
演示:
Map<String, String> left = ImmutableMap.of("1", "1", "2", "2", "3", "3");
Map<String, String> right = ImmutableMap.of("2", "2", "3", "3333", "4", "4");
MapDifference<String, String> difference = Maps.difference(left, right);
System.out.println("左边 Map 独有的 key:" + difference.entriesOnlyOnLeft());
System.out.println("右边 Map 独有的 key:" + difference.entriesOnlyOnRight());
System.out.println("左右 Map 都有的 key,并且 value 相等:" + difference.entriesInCommon());
System.out.println("左右 Map 都有的 key,但是 value 不相等:" + difference.entriesDiffering());
执行结果:
左边 Map 独有的 key:{1=1}
右边 Map 独有的 key:{4=4}
左右 Map 都有的 key,并且 value 相等:{2=2}
左右 Map 都有的 key,但是 value 不相等:{3=(3, 3333)}
从这个 demo 我们可以看到此方法的强大威力,我们在工作中经常遇到 Map 或者 List 间差异比较的任务,我们就可以直接使用该方法进行比对,List 可以先转换成 Map。
而且 difference 底层的实现也算是最优的实现了,只需要循环一遍,就可以得到上述四种差异结果,源码如下:
private static <K, V> void doDifference(
// 左边的 map
Map<? extends K, ? extends V> left,
// 右边的 map
Map<? extends K, ? extends V> right,
// value 比较器
Equivalence<? super V> valueEquivalence,
// key 只在左边 map 出现
Map<K, V> onlyOnLeft,
// key 只在右边 map 出现(该 map 内容为 right 所有,
// ==> Map<K, V> onlyOnRight = new LinkedHashMap<>(right))
Map<K, V> onlyOnRight,
// key 在左右 map 都有,并且 value 相等
Map<K, V> onBoth,
// key 在左右 map 都有,但是 value 不相等
Map<K, MapDifference.ValueDifference<V>> differences) {
// 以左边 map 为基准循环
for (Entry<? extends K, ? extends V> entry : left.entrySet()) {
K leftKey = entry.getKey();
V leftValue = entry.getValue();
// 如果右边 map 包含左边 map 的 key
if (right.containsKey(leftKey)) {
// 从右边独有 map 移除该 key
V rightValue = onlyOnRight.remove(leftKey);
// 如果左右 map 值相等,则加入到 onBoth
if (valueEquivalence.equivalent(leftValue, rightValue)) {
onBoth.put(leftKey, leftValue);
}
// 反之则加入到 differences
else {
differences.put(leftKey, ValueDifferenceImpl.create(leftValue, rightValue));
}
}
// 如果右边 map 不包含左边 map 的 key
else {
// 则表示该 key 是左边 map 独有的,加入到 onlyOnLeft 即可
onlyOnLeft.put(leftKey, leftValue);
}
}
}
这是一种比较优秀的,快速比对的算法,可以好好看下上面的源码,然后把这种算法背下来。
Sets 的使用方式和 Lists 和 Maps 很类似。
4 总结
在 Guava 对集合的设计中,有两个大点是非常值得我们学习的:
- Lists、Maps 的出现给我们提供了更方便的使用姿势和方法,我们在实际工作中,如果碰到特别繁琐,或者特别难用的 API,我们也可以进行一些包装,使更好用,这个是属于在解决目前的痛点问题上的创新,是非常值得提倡的一件事情,往往可以帮助你拿到更好的绩效。
- 如果有人问你,List 或者 Map 高效的差异排序算法,完全可以参考 Maps.diffenerce 的内部实现,该方法只使用了一次循环,就可以得到所有相同或不同结果,这种算法在我们工作中也经常被使用。
------------------------------------- END -------------------------------------