基于版本:Guava 22.0

Wiki:Ordering

0. Ordering简介

Guava的Ordering提供了链式风格的比较器的实现,我们可以用Ordering轻松构建复杂的比较器。

1. 类图

这张类图不完全,实际上Ordering有十几个子类,这些子类共同提供了复杂的功能。

2. 设计思路

Ordering是继承于java.util.Comparator接口的抽象类,它的十几个子类都实现了compare与equals方法,这些子类可以实现基本的排序功能。

通过链式调用,可以将这些子类组合在一起,实现复杂的排序规则。

举个例子:

Ordering combineOrdering = Ordering.natural().reverse().nullsFirst();

对应的源码如下

Ordering.nullsFirst()
@GwtCompatible(serializable = true)
public <S extends T> Ordering<S> nullsFirst() {
return new NullsFirstOrdering<S>(this);
} Ordering.reverse()
@GwtCompatible(serializable = true)
public <S extends T> Ordering<S> reverse() {
return new ReverseOrdering<S>(this);
} NullsFirstOrdering.class
final Ordering<? super T> ordering; NullsFirstOrdering(Ordering<? super T> ordering) {
this.ordering = ordering;
} @Override
public int compare(@Nullable T left, @Nullable T right) {
if (left == right) {
return 0;
}
if (left == null) {
return RIGHT_IS_GREATER;
}
if (right == null) {
return LEFT_IS_GREATER;
}
return ordering.compare(left, right);
} ReverseOrdering.class
final Ordering<? super T> forwardOrder; ReverseOrdering(Ordering<? super T> forwardOrder) {
this.forwardOrder = checkNotNull(forwardOrder);
} @Override
public int compare(T a, T b) {
return forwardOrder.compare(b, a);
}

可以看出combineOrdering实际上是由外部的NullsFirstOrdering加上内部ReverseOrdering嵌套而成的

在调用combineOrdering.compare方法的时候,会先调用NullsFirstOrdering.compare方法,再调用ReverseOrdering.compare方法

这会导致排序完毕的集合,null元素在最前面,后面则是逆向元素

比方[2,1,3,null,0]的排序结果就是[null,3,2,1,0]

这就是链式调用的特点,从右向左,逐渐应用排序规则

3. CompoundOrdering

设想一个场景,我们需要对学生的月考成绩从高到低进行排序,如果两人分数相同,则按照学号排序。

很显然这两个规则对应于两个简单的比较器,但是如何将这两个排序规则结合起来呢?

此时我们需要CompoundOrdering

Ordering.compound方法会创建CompoundOrdering的实例,相关源码如下

Ordering.compound()
public <U extends T> Ordering<U> compound(Comparator<? super U> secondaryComparator) {
return new CompoundOrdering<U>(this, checkNotNull(secondaryComparator));
} public static <T> Ordering<T> compound(Iterable<? extends Comparator<? super T>> comparators) {
return new CompoundOrdering<T>(comparators);
} CompoundOrdering.class
final ImmutableList<Comparator<? super T>> comparators; CompoundOrdering(Comparator<? super T> primary, Comparator<? super T> secondary) {
this.comparators = ImmutableList.<Comparator<? super T>>of(primary, secondary);
} CompoundOrdering(Iterable<? extends Comparator<? super T>> comparators) {
this.comparators = ImmutableList.copyOf(comparators);
} @Override
public int compare(T left, T right) {
// Avoid using the Iterator to avoid generating garbage (issue 979).
int size = comparators.size();
for (int i = 0; i < size; i++) {
int result = comparators.get(i).compare(left, right);
if (result != 0) {
return result;
}
}
return 0;
}

很容易可以看出,CompoundOrdering内部维护了一个比较器列表,调用CompoundOrdering.compare方法时,会从头遍历这个列表,直到找到一个compare方法不返回0的比较器为止。

这样就实现了如果前面的比较器无法区分大小则调用后续的比较器进一步区分的语义。

与第二节讲到的链式调用的从右向左阅读规则不同的是,CompoundOrdering的阅读规则是从左向右。

Wiki上还特意提到(Exception to the "backwards" rule: For chains of calls to compound, read from left to right. To avoid confusion, avoid intermixing compound calls with other chained calls.) 为了避免理解上的混乱,请不要把compound写在一长串链式调用的中间,你可以另起一行,在链中最先或最后调用compound。

4. ByFunctionOrdering

Ordering的onResultOf方法提供了对先对集合内元素调用函数,然后再调用自定义比较器的解决方案

Ordering.onResultOf
public <F> Ordering<F> onResultOf(Function<F, ? extends T> function) {
return new ByFunctionOrdering<F, T>(function, this);
} ByFunctionOrdering.class
final class ByFunctionOrdering<F, T> extends Ordering<F> implements Serializable {
final Function<F, ? extends T> function;
final Ordering<T> ordering; ByFunctionOrdering(Function<F, ? extends T> function, Ordering<T> ordering) {
this.function = checkNotNull(function);
this.ordering = checkNotNull(ordering);
} @Override
public int compare(F left, F right) {
return ordering.compare(function.apply(left), function.apply(right));
}

代码非常简单,构造器中传入函数function与自定义比较器,然后compare方法中调用自定义比较器,对被function处理过的对象进行比较排序

举个例子,我们现在有一个实体类Student,里面有两个属性:String型的name,与Integer型的grade,现在我们需要对一个Student列表按照grade从高到低排序。

需要的比较器如下

    public static void main(String[] args) {
Ordering combineOrdering =
Ordering.natural().reverse().onResultOf(new Function<Student, Comparable>() {
@Nullable
@Override
public Comparable apply(@Nullable Student input) {
return
input.getGrade();
}
});

List<Student> list = Lists.newArrayList();
list.add(new Student("a", 60));
list.add(new Student("b", 90));
list.add(new Student("c", 80));
list.add(new Student("d", 30));
Collections.sort(list, combineOrdering);
System.out.println(list);
}

输出结果如下:

[Student{name='b', grade=90}, Student{name='c', grade=80}, Student{name='a', grade=60}, Student{name='d', grade=30}]

可以看到,结果确实被很好的排序了

5. Ordering提供的一些实用方法

Ordering.max()//遍历输入集合,取最大值
@CanIgnoreReturnValue // TODO(kak): Consider removing this
public <E extends T> E max(Iterator<E> iterator) {
// let this throw NoSuchElementException as necessary
E maxSoFar = iterator.next(); while (iterator.hasNext()) {
maxSoFar = max(maxSoFar, iterator.next());
} return maxSoFar;
} Ordering.min()//遍历输入集合,取最小值
@CanIgnoreReturnValue // TODO(kak): Consider removing this
public <E extends T> E min(Iterator<E> iterator) {
// let this throw NoSuchElementException as necessary
E minSoFar = iterator.next(); while (iterator.hasNext()) {
minSoFar = min(minSoFar, iterator.next());
} return minSoFar;
} Ordering.greatestOf()//对比较器做逆序操作,然后取逆序后的比较器的最小的k个值
public <E extends T> List<E> greatestOf(Iterator<E> iterator, int k) {
return reverse().leastOf(iterator, k);
} Ordering.leastOf
public <E extends T> List<E> leastOf(Iterator<E> iterator, int k) {
checkNotNull(iterator);
checkNonnegative(k, "k"); if (k == 0 || !iterator.hasNext()) {
return ImmutableList.of();
} else if (k >= Integer.MAX_VALUE / 2) {//如果k很大,则直接完全排序然后取头k个结果并返回
// k is really large; just do a straightforward sorted-copy-and-sublist
ArrayList<E> list = Lists.newArrayList(iterator);
Collections.sort(list, this);
if (list.size() > k) {
list.subList(k, list.size()).clear();
}
list.trimToSize();//压缩数组以释放内存
return Collections.unmodifiableList(list);
} else {//用quickselect算法取top k
TopKSelector<E> selector = TopKSelector.least(k, this);
selector.offerAll(iterator);
return selector.topK();
}
} Ordering.isOrdered()//遍历输入集合,检查集合是否有序
public boolean isOrdered(Iterable<? extends T> iterable) {
Iterator<? extends T> it = iterable.iterator();
if (it.hasNext()) {
T prev = it.next();
while (it.hasNext()) {
T next = it.next();
if (compare(prev, next) > 0) {
return false;
}
prev = next;
}
}
return true;
}

代码很简单

只有Ordering.leastOf方法有点意思,它会根据传入的k值分情况处理。

如果k < Integer.MAX_VALUE / 2,则调用经典的quickselect算法取TOP K。

如果k >= Integer.MAX_VALUE / 2,则先拷贝传入的集合,然后做全排序,然后取头k个结果,然后再压缩剩余的元素以节省内存。

为什么要这么设计,我不太明白。

我个人的想法是,既然Java的设定是一般集合的大小不能超过Integer.MAX_VALUE,那么如果k >= Integer.MAX_VALUE / 2,直接将比较器反向,然后取TOP(size - k)不就好了吗?

Guava源码学习(二)Ordering的更多相关文章

  1. Dubbo源码学习(二)

    @Adaptive注解 在上一篇ExtensionLoader的博客中记录了,有两种扩展点,一种是普通的扩展实现,另一种就是自适应的扩展点,即@Adaptive注解的实现类. @Documented ...

  2. python 协程库gevent学习--gevent源码学习(二)

    在进行gevent源码学习一分析之后,我还对两个比较核心的问题抱有疑问: 1. gevent.Greenlet.join()以及他的list版本joinall()的原理和使用. 2. 关于在使用mon ...

  3. Vue源码学习二 ———— Vue原型对象包装

    Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...

  4. 以太坊 layer2: optimism 源码学习(二) 提现原理

    作者:林冠宏 / 指尖下的幽灵.转载者,请: 务必标明出处. 掘金:https://juejin.im/user/1785262612681997 博客:http://www.cnblogs.com/ ...

  5. [spring源码学习]二、IOC源码——配置文件读取

    一.环境准备 对于学习源码来讲,拿到一大堆的代码,脑袋里肯定是嗡嗡的,所以从代码实例进行跟踪调试未尝不是一种好的办法,此处,我们准备了一个小例子: package com.zjl; public cl ...

  6. Guava源码学习(五)EventBus

    基于版本:Guava 22.0 Wiki:EventBus 0. EventBus简介 提供了发布-订阅模型,可以方便的在EventBus上注册订阅者,发布者可以简单的将事件传递给EventBus,E ...

  7. SocketServer源码学习(二)

    SocketServer 中非常重要的两个基类就是:BaseServer 和 BaseRequestHandler在SocketServer 中也提供了对TCP以及UDP的高级封装,这次我们主要通过分 ...

  8. Guava源码学习(零)前言

    Guava是由Google出品的Java类库,功能强大且易用. 后续我会用多篇博客介绍Guava的使用方法,以及从源码层面分析其实现原理. 分析次序基于Guava的官方Wiki 基于版本:Guava ...

  9. Thrift源码学习二——Server层

    Thrift 提供了如图五种模式:TSimpleServer.TNonblockingServer.THsHaServer.TThreadPoolServer.TThreadSelectorServe ...

随机推荐

  1. Maven学习 (五) Elipse中发布一个Maven项目到Tomcat

    对于maven初学者的我,经常遇到一个问题就是,maven项目创建成功后,本来已经添加了jar的依赖,但是发布到Tomcat中就是没有jar包存在, 启动Tomcat总是报没有找到jar包,可项目结构 ...

  2. Java 注解(Annoation)学习笔记

    1 Junit中的@Test为例: 1.1 用注解(@Test)前 private boolean isTestMethod(Method m) { return m.getParameterType ...

  3. 《Cracking the Coding Interview》——第11章:排序和搜索——题目2

    2014-03-21 20:49 题目:设计一种排序算法,使得anagram排在一起. 解法:自定义一个comparator,使用额外的空间来统计字母个数,然后比较字母个数. 代码: // 11.2 ...

  4. 《Cracking the Coding Interview》——第5章:位操作——题目2

    2014-03-19 05:47 题目:给定一个double型浮点数,输出其二进制表示,如果不能在32个字符内完成输出,则输出“ERROR”. 解法:如果你熟悉IEEE754标准,应该知道double ...

  5. asp.net 身份验证-Form 身份验证

    一. .net身份验证简介 1.身份验证就是检测用户是否登录及所访问的资源是否有权限.当我们在访问一个受保护网络资源时,往往需要输入用户名.密码信息,或通过其他证书.第三方身份验证等方式.验证(Aut ...

  6. python3 打印九九乘法口诀表

    for i in range(1, 10): for j in range(1, i+1): # print(f'{i}×{j}={i*j}', end='\t') print('%d×%d=%d' ...

  7. appium-在页面点击一下处理(一般处理提示蒙层)

    在写用例的时候,经常会发现某些第一次进去的页面会有一个蒙层提示.我们只有处理掉这个蒙层,才能继续我们的用例: 这边我们使用的是TouchAction的tap方法 TouchAction action ...

  8. Kotlin中功能操作与集合(KAD 11)

    作者:Antonio Leiva 时间:Feb 2, 2017 原文链接:https://antonioleiva.com/functional-operations-collections-kotl ...

  9. Python 高级 I/O 多路复用

    Table of Contents 前言 select selectors 结语 参考链接 前言 第一次接触和 I/O 多路复用相关的概念是在书 CSAPP1 的并发编程章节,当时在了解了这个概念后只 ...

  10. unity生命周期

    1.静态构造函数 当程序集被加载的时候就被调用了,如果你的unity处于编辑状态时,此时你保存一个脚本(从而迫使重新编译),静态构造函数会立即被调用,因为unity加载了DLL.并且它将不会再次运行, ...