Collections.sort()方法 是用来对 List 进行排序的,主要有两种方式。
1、List 中的对象继承Comparable接口,并实现 接口中的 compareTo 方法
2、Collections.sort 方法重载
现在先讨论下第一种方法。
List 对象为什么要实现compareTo 方法呢?这种排序方式的关键步骤有哪些 ?这就是我今天写代码时所纠结的地方。下面,以网上找的一段代码为切入点,探讨一下这种排序方式的实现原理:
//先定义需要比较的对象类
public class PersonH implements Comparable<PersonH>
{
private int level;
public Integer getLevel()
{
return level;
}
public void setLevel(Integer level1)
{
this.level = level1;
}
<span style="color:#3333FF;">@Override
public int compareTo(PersonH o)
{
// TODO Auto-generated method stub
return this.getLevel().compareTo(o.getLevel());
} </span>
}
//测试类
public class Main
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO Auto-generated method stub
PersonH person = new PersonH();
person.setLevel(6);
PersonH person2 = new PersonH();
person2.setLevel(8);
PersonH person3 = new PersonH();
person3.setLevel(3);
ArrayList<PersonH> personList = new ArrayList<PersonH>();
personList.add(person);
personList.add(person2);
personList.add(person3);
<span style="color:#3333FF;">Collections.sort(personList);</span>//排序
for(PersonH personH : personList) //输出排序后结果
{
System.out.println(personH.getLevel());
}
}
这是一段比较典型的List 中的对象继承Comparable接口 进而进行排序的方法。
要注意,这个对象类实现了Comparable接口,点开Comparable接口
public interface Comparable<T> {
/**
* Compares this object with the specified object for order. Returns
* from being compared to this object.
。。。。这里有一堆
*/
public int compareTo(T o);
}
会发现,这个接口就一个compareTo 方法。
再看 测试类, 这里将比较类的对象加到了一个personList中,并作为Collections.sort(personList)的参数。
这个比较方法 就这么一个参数,具体排序功能是怎么实现的呢?点开源码查看sort方法。
public static void sort(EList<?> list)
{
Object[] listAsArray = list.toArray();
Arrays.sort(listAsArray);
for (int i=0; i < listAsArray.length; i++)
{
int oldIndex = indexOf(list, listAsArray[i], i);
if (i != oldIndex)
{
list.move(i, oldIndex);
}
}
}
这里先把list转化为对象数组,然后调用Arrays.sort(listAsArray)中的sort方法。
然后看这里的sort()方法有何不同。
public static void sort(Object[] a) {
Object[] aux = (Object[])a.clone();
mergeSort(aux, a, 0, a.length, 0);
}
//① clone方法(如果我们要用aux保存a对象的数据,但又不希望aux对象数据被改变时不影响到a。实现clone()方法是其一种最简单,也是最高效的手段。
// ② 这里的sort()方法参数是Object[]数组。
接着看mergeSort():
/**
* Src is the source array that starts at index 0
* Dest is the (possibly larger) array destination with a possible offset
* low is the index in dest to start sorting
* high is the end index in dest to end sorting
* off is the offset to generate corresponding low, high in src
*/
private static void mergeSort(Object[] src,
Object[] dest,
int low,
int high,
int off) {
int length = high - low;
// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
for (int j=i; j>low &&
((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
int destLow = low;
int destHigh = high;
low += off;
high += off;
int mid = (low + high) >>> 1;
mergeSort(dest, src, low, mid, -off);
mergeSort(dest, src, mid, high, -off);
// If list is already sorted, just copy from src to dest. This is an
// optimization that results in faster sorts for nearly ordered lists.
if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
System.arraycopy(src, low, dest, destLow, length);
return;
}
// Merge sorted halves (now in src) into dest
for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
}
我们将代码分段来看:
private static void mergeSort(Object[] src,
Object[] dest,
int low,
int high,
int off) {
int length = high - low;
// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
for (int j=i; j>low &&
((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
输入参数dest[ ] 就是原来那个对象数组,对象重写的那个compareTo方法就用上了,写到这里就能解释为什么对象数组要将compareTo方法重写了,这就解决了第一个问题。
这一步(Comparable) dest[j-1]).compareTo(dest[j])>0;j–) 中强转成 Comparable 类型什么意思呢?
((Comparable)dest[j-1]) 这样,先强转成Comparable类型,然后才能再用comparaTo方法。
这里有个常量INSERTIONSORT_THRESHOLD ,为什么要有这么一个常量呢?
/**
* Tuning parameter: list size at or below which insertion sort will be
* used in preference to mergesort or quicksort.
*/
private static final int INSERTIONSORT_THRESHOLD = 7;
当长度少于7的时候,使用直接插入排序。插入排序适用于小数列的排序,提高了程序运行的效率。
当长度大于7的时候,程序先将数组拆分成低区间和高区间两个区间,再调用两个递归对这两个区间元素进行排序。在递归时还得推断已经划分的区间元素是否还多于7位,假设多于7位的话继续划分成两个区间,这样循环递归调用。
// Recursively sort halves of dest into src
int destLow = low;
int destHigh = high;
low += off;
high += off;
int mid = (low + high) >>> 1;
mergeSort(dest, src, low, mid, -off);
mergeSort(dest, src, mid, high, -off);
第6行为什么用>>>呢?(知乎上这里说的很清楚http://www.zhihu.com/question/20101702)
这里开始递归排序,我们不需要关注off变量,这个变量是排序数组中部分区域的时候使用的,而我们要排序的是整个数组
// If list is already sorted, just copy from src to dest. This is an
// optimization that results in faster sorts for nearly ordered lists.
if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
System.arraycopy(src, low, dest, destLow, length);
return;
}
左边和右边排好序之后,开始合并。这时src[low ~ mid – 1]和src[mid ~ high – 1]都是有序的,这时比较src[mid – 1]和src[mid],如果前者比后者小,那么整个src数组就是有序的了,只需将其复制到目标数组后,就完成了排序,这种碰运气的做法感觉并没有什么卵用,这种情况太少啦!
// Merge sorted halves (now in src) into dest
for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
这里就是传统的合并操作了。两个排好序的数组不断进行比较,将比较好的元素从小到大加到另一个数组中。
回过头来看第三段代码,这段代码执行完,listAsArry 就成了一个有序的对象数组了。接着进入到 for 循环里,这个for 循环有什么作用呢?
public static void sort(EList<?> list)
{
Object[] listAsArray = list.toArray();
Arrays.sort(listAsArray);
for (int i=0; i < listAsArray.length; i++)
{
int oldIndex = indexOf(list, listAsArray[i], i);
if (i != oldIndex)
{
list.move(i, oldIndex);
}
}
}
查看IndexOf()方法:
大概读了一下,这个方法作用是在list中从角标fromIndex开始查找对象o所在的位置,不存在返回 -1.
public static int indexOf(List<?> list, Object o, int fromIndex)
{
if (fromIndex < 0)
{
fromIndex = 0;
}
int size = list.size();
for (int i = fromIndex; i < size; i++)
{
Object element = list.get(i);
if (o == null)
{
if (element == null)
{
return i;
}
}
else if (o == element || o.equals(element))
{
return i;
}
}
return -1;
}
这个方法放到这段代码中的含义就是比较同一个元素在 list 中与排序好的 listAsArray 中的角标是否相同。如果不同,则调用list.move()方法。
/**
* Moves the object from the old position to the new position.
* @param newPosition the position of the object after the move.
* @param oldPosition the position of the object before the move.
* @return the moved object.
*/
E move(int newPosition, int oldPosition);
可以看出这个move()的作用是,将list 中的某个对象从旧位置移到新位置。 放到这段代码中,move的作用就是将list 中的元素按照 listAsArray 中元素的位置顺序进行排序。得到的 List 就是sort() 方法输入的并且已经排好序的 list 。
综上,看出这个排序方式的主要实现步骤有三个:
1、将输入参数 List 转换为一个对象数组。
2、 将对象数组传给Arrays类的sort方法(实际上这个排序本质上是调用Arrays.sort(),这也体现了面向对象的代码复用这一特性)。
3、排序完成后,调用list.move() 方法将list中的元素按照数组中的元素顺序进行排序。
个人认为实际应用的时候只要记住用法就好了,可以深究,但是真的不必每一个点都去深究,东西太多,而时间又太少。。。