基于版本: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. 哪些工具能有效管理Azure Active Directory?

    [TechTarget中国原创] 管理Azure Active Directory有四种常见的工具:Azure Web门户.Azure PowerShell.Azure命令行接口和Azure Mana ...

  2. gulp相关

    'use strict'; var gulp = require('gulp'), webserver = require('gulp-webserver'), //gulp服务器 connect = ...

  3. Python 两种方式实现斐波那契数列

    斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946 ...

  4. Eclipse安装使用

    1.访问https://www.eclipse.org/downloads/下载最新的Eclipse工具包或者百度通过其他路径下载需要的版本 2.下载完成后将压缩包进行解压的得到相应的文件 3.进入解 ...

  5. PHP如何实现第三方分享

    <!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. android自定义SlideMenu源码详解之最简单侧滑实现

    实现原理:在一个Activity的布局中需要有两部分,一个是菜单(menu)的布局,一个是内容(content)的布局.两个布局横向排列,菜单布局在左,内容布局在右.初始化的时候将菜单布局向左偏移,以 ...

  7. hdu 1085 给出数量限制的母函数问题 Holding Bin-Laden Captive!

    Holding Bin-Laden Captive! Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Ja ...

  8. 启动Tomcat时的常见问题及解决办法

    问题一:环境变量 1.检查jdk 验证jdk的配置,在运行-cmd中输入 java -version 即表示安装成功. 如果jdk没有问题,还需要配置两个环境变量.找到jdk和jre的路径,配置JAV ...

  9. Java获取当前服务器IP实现

    package hope.ipaddress.demo; import java.net.InetAddress; import java.net.NetworkInterface; import j ...

  10. 【bzoj2424】[HAOI2010]订货 费用流

    原文地址:http://www.cnblogs.com/GXZlegend/p/6825296.html 题目描述 某公司估计市场在第i个月对某产品的需求量为Ui,已知在第i月该产品的订货单价为di, ...