最近Algorithms 4 课上提到了排序。趁着这个机会,梳理一下。java
Comparable<T>
接口和Comparator<T>
接口都是JDK中提供的和比较相关的接口。使用它们能够对对象进行比较大小,排序等操做。这算是以后排序的先导知识吧。
Comparable
, 字面意思是“能够比较的”,因此实现它的类的多个实例应该能够相互比较“大小”或者“高低”等等。
Comparator
, 字面意思是“比较仪,比较器”, 它应该是专门用来比较用的“工具”。面试
Comparable<T>
接口数组
public interface Comparable<T> { public int compareTo(T o); }
首先看看JDK中怎么说的:架构
This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's <i>natural ordering</i>, and the class's <tt>compareTo</tt> method is referred to as its <i>natural comparison method</i>.<p>框架
大意是: 任何实现这个接口的类,其多个实例能以固定的次序进行排列。次序具体由接口中的方法compareTo
方法决定。分布式
Lists (and arrays) of objects that implement this interface can be sorted automatically by {@link Collections#sort(List) Collections.sort} (and {@link Arrays#sort(Object[]) Arrays.sort}).工具
若是某个类实现了这个接口,则它的List
或数组都能使用Collections.sort()
或Arrays.sort()
进行排序。
常见的类如Integer
, Double
, String
都实现了此类。一下子会结合源码进行分析。源码分析
咱们先来看Integer
中的实现:性能
public final class Integer extends Number implements Comparable<Integer> { private final int value; public int compareTo(Integer anotherInteger) { return compare(this.value, anotherInteger.value); } public static int compare(int x, int y) { return (x < y) ? -1 : ((x == y) ? 0 : 1); } public static int compareUnsigned(int x, int y) { return compare(x + MIN_VALUE, y + MIN_VALUE); }
咱们只贴出了和比较相关的方法。
能够看到,compareTo
方法其中调用了compare
方法,这是JDK1.7增长的方法。在Integer
中新增这个方法是为了减小没必要要的自动装箱拆箱。传入compare
方法的是两个Integer
的值x
和y
。
若是x < y
, 返回-1
;若是x = y
, 返回0
;若是x > y
, 返回1
。
顺便一说,JDK中的实现很是简洁,只有一行代码, 当判断状况有三种时,使用这种嵌套的判断 x ? a : b
能够简洁很多,这是该学习的。学习
后面的compareUnsigned
是JDK1.8新加入的方法, 用来比较无符号数。这里的无符号数意思是默认二进制最高位再也不做为符号位,而是计入数的大小。
其实现是
public static int compareUnsigned(int x, int y) { return compare(x + MIN_VALUE, y + MIN_VALUE); }
直接为每一个值加了Integer
的最小值 -231。咱们知道Java中int
类型为4个字节,共32位。符号位占用一位的话,则其范围为-231 到231 - 1。
使用此方法时,全部正数都比负数小。最大值为 -1
,由于 -1
的二进制全部位均为 1。
也就是1111 1111 1111 1111 1111 1111 1111 1111
> 其它任何32位数。
具体是什么状况呢?
首先咱们知道,在计算机中,全部数都是以二进制存在,也就是0
和1
的组合。
为了使数字在计算机中运算不出错,出现了原码,反码和补码。原码就是一个数的二进制表示,其中最高位为符号位,表示其正负。
正数的原码反码补码都同样,负数的反码是除符号位之外所有取反,补码为反码加1,如图所示为32位bits(也就是4比特bytes)数的原码反码和补码。
原码反码补码.jpg
为何要使用反码和补码呢?用四位二进制数举例:
1
的二进制为0001
,-1
的二进制为1001
,若是直接相加,则1 + (-1) = 0
,二进制表示为0001 + 1001 = 1010 != 0
,因此不能直接使用原码作运算。
后来出现了反码,除符号位以外其余位取反,1001(-1)
取反后为1110
, 如今作加法 0001 (1) + 1110 (-1) = 1111
。因为1111
是负数,因此取反以后才是其真实值,取反后为1000
,也就是-0
。这能知足条件了,可是美中不足的是,0
带了负号。惟一的问题其实就出如今0
这个特殊的数值上。 虽然人们理解上+0
和-0
是同样的, 可是0带符号是没有任何意义的。 并且会有0000
原和1000
原两个编码表示0
。怎么办呢?
人们又想出了补码,它是反码加1
。-1
的补码是 1111
,以上的运算用补码表示就是0001 (1) + 1111 (-1) = 0000 = 0
。神奇的发现,这个式子完美契合了十进制加法!
同时咱们留出了1000
,能够用它表示-8
(-1) + (-7) = (补码) 1111 + 1001 = 1000 = -8
。注意,因为此处的-8
使用了以前-0
的补码来表示,因此-8
没有没有原码和反码表示(针对的四位,若是是八位,则没有原码和反码的是-128
,依次类推)。
使用补码, 不只仅修复了0
的符号以及存在两个编码的问题, 并且还可以多表示一个最低数. 这就是为何4位二进制, 使用原码或反码表示的范围为[-7, +7]
, 而使用补码表示的范围为[-8, 7]
.
二进制加法.jpg
这就是简单的要用反码和补码的缘由。
int
类型在32位系统中占4个字节、32bit,补码表示的的数据范围为:
[10000000 00000000 00000000 00000000] ~ [01111111 11111111 11111111 11111111]
[−231,231−1]
[-2147483648, 2147483647]
在java中表示为:
[Integer.MIN_VALUE, Integer.MAX_VALUE]
与byte
类型的表示同样,因为负数比正数多表示了一个数字。对下限去相反数后的数值会超过上限值,溢出到下限,所以下限的相反数与下限相等;对上限去相反数的数值为负值,该负值比下限的负值大1,在能够表示的范围内,所以上限的相反数是上限直接取负值。
看完Integer
后,咱们再来看String
中compareTo
的实现方式:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; // String的值 public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); // limit, 表示两个String中长度较小的String长度 char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; // 若是char不相同,则取其差值 } k++; // 若是char值相同,则继续日后比较 } return len1 - len2; // 若是全部0 ~ (lim - 1)的char均相同,则比较两个String的长短 } // 字面意思是对大小写不敏感的比较器 public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable { private static final long serialVersionUID = 8575799808933029326L; public int compare(String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); // 和上面相似,均是取两个String间的最短长度 for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); // 统一换成大写 c2 = Character.toUpperCase(c2); // 统一换成大写 if (c1 != c2) { // 大写若是不相等则再换为小写试试 c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // 到此处则肯定不相等 // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } /** Replaces the de-serialized object. */ private Object readResolve() { return CASE_INSENSITIVE_ORDER; } } // String的方法,能够直接使用这个方法和其它String进行比较, // 内部实现是调用内部比较器的compare方法 public int compareToIgnoreCase(String str) { return CASE_INSENSITIVE_ORDER.compare(this, str); } }
String
中的关于compare
的方法相对复杂一点,但仍是比较简单。咱们先不看其余的代码,只重点关注compareTo
方法。
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); // limit, 表示两个String中长度较小的String长度 char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; // 若是char不相同,则取其差值 } k++; // 若是char值相同,则继续日后比较 } return len1 - len2; // 若是全部0 ~ (lim - 1)的char均相同,则比较两个String的长短 }
内容很简洁,就是取两个String
的长度中较小的,做为限定值(lim
)。以后对数组下标为从0
到lim - 1
的char
变量进行遍历比较,若是遇到不相同的值,返回其差值。通常咱们只用其正负性,若是返回负数则说明第一个对象比第二个对象“小”。
例如比较 "abc"
和"bcd"
,当对各自第一个字符'a'
和 'b'
进行比较时,发现 'a' != 'b'
,则返回 'a' - 'b'
,这个值是负数, char
类型的-1
,Java会自动将其类型强转为int
型。最后得出结论"abc"
比"bcd"
小。
Comparator<T>
接口
public interface Comparator<T> { int compare(T o1, T o2); }
这是一个外部排序接口,它的功能是规定“比较大小”的方式。实现它的类能够做为参数传入Collections.sort()
或Arrays.sort()
,使用它的比较方式进行排序。
它能够为没有实现Comparable
接口的类提供排序方式。
String
类中以及Array
类等都有实现此接口的内部类。
在上面String
的源码中就有一个内部的自定义Comparator
类CaseInsensitiveComparator
, 咱们看看它的源码。
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable { private static final long serialVersionUID = 8575799808933029326L; public int compare(String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); // 和上面相似,均是取两个String间的最短长度 for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); // 统一换成大写 c2 = Character.toUpperCase(c2); // 统一换成大写 if (c1 != c2) { // 大写若是不相等则再换为小写试试 c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // 到此处则肯定不相等 // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } /** Replaces the de-serialized object. */ private Object readResolve() { return CASE_INSENSITIVE_ORDER; } } // String的方法,能够直接使用这个方法和其它String进行比较, // 内部实现是调用内部比较器的compare方法 public int compareToIgnoreCase(String str) { return CASE_INSENSITIVE_ORDER.compare(this, str); } }
CaseInsensitiveComparator
, 字面意思是对大小写不敏感的比较器。
咱们观察它的compare
方法,能够发现,它和上面的compareTo
方法实现相似,都是取两个String
中长度较小的,做为限定值min
,以后对数组下标为从0
到min - 1
的char
变量进行遍历比较。和上面稍有不一样的是,此处先将char
字符统一换成大写(upper case), 若是仍然不相等,再将其换为小写(lower case)比较。一个字母只有大写或者小写两种情形,若是这两种状况都不想等则肯定不相等,返回其差值。若是限定值内全部的char
都相等的话,再去比较两个String
类型的长度。
例如比较 "abC"
和"ABc"
,compareTo
会直接返回 'a' - 'A'
,而compareToIgnoreCase
方法因为使用了CaseInsensitiveComparator
,比较结果最终会返回true
。
感兴趣能够加Java架构师群获取Java工程化、高性能及分布式、高性能、深刻浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货的直播免费学习权限 都是大牛带飞 让你少走不少的弯路的 群..号是:855801563 对了 小白勿进 最好是有开发经验
注:加群要求
一、具备工做经验的,面对目前流行的技术不知从何下手,须要突破技术瓶颈的能够加。
二、在公司待久了,过得很安逸,但跳槽时面试碰壁。须要在短期内进修、跳槽拿高薪的能够加。
三、若是没有工做经验,但基础很是扎实,对java工做机制,经常使用设计思想,经常使用java开发框架掌握熟练的,能够加。
四、以为本身很牛B,通常需求都能搞定。可是所学的知识点没有系统化,很难在技术领域继续突破的能够加。
5.阿里Java高级大牛直播讲解知识点,分享知识,多年工做经验的梳理和总结,带着你们全面、科学地创建本身的技术体系和技术认知!