Java对象排序、中文排序、SortedSet排序使用和源码讲解
作者:网络转载 发布时间:[ 2016/3/4 10:30:53 ] 推荐标签:编程语言 测试开发技术
源码片段5:mergeSort(Object[]src , Object[]dst , int low , int high , int off)
private static void mergeSort(Object[] src,
Object[] dest,
int low,
int high,
int off) {
int length = high - low;
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 (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
System.arraycopy(src, low, dest, destLow, length);
return;
}
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++];
}
}
/**
* Swaps x[a] with x[b].
*/
private static void swap(Object[] x, int a, int b) {
Object t = x[a];
x[a] = x[b];
x[b] = t;
}
仔细阅读代码可以发现排序是分段递归回调的方式来排序(注意中间的low和high两个参数的变化),每次如果分段的大小大于INSERTIONSORT_THRESHOLD(定义为7)的时候,则再分段,前一段和后一段,然后分开的两段再调用递推,递推后再回归排序,若发现中间分隔的位置两个数据是有序,则认为两段是完全有序的,若不是,那么再将两段做一次排序,此时排序很好排序了,因为两个块是排序排好的,所以不需要两次循环,只需要循环扫描下去,两个数组按照顺序向下走,分别对比出小值写入数组,较大者暂时不写入数组与另一个数组的下一个值进行对比,后一截数据(源码中是通过越界来判定的)写入到尾巴当中:
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++];
}
这段对两个有序数组的排序是很经典的写法,主要是if语句的浓缩,不然代码会写得很长。
注意:这里的代码排序中使用了强制类型转换为Comparable来调用内部的comareTo方法,所以如果你的类没有implements Comparable那么在Collections.sort(List<T>list)时编译时会报错上面已经说到,在调用Arrays.sort(Object []t)时,编译时并不会报错,但是运行时会报错为:java.lang.ClassCastExceptionXXXDO cannot be cast to java.lang.Comparable
排序部分我们再看看其重载的mergeSort方法,是传入了自定义的Comparator的方法
源码片段6: mergeSort(Object[]src,Object[]dst,int low,int high,intoff,Comparator c)
private static void mergeSort(Object[] src,
Object[] dest,
int low, int high, int off,
Comparator c) {
int length = high - low;
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
for (int j=i; j>low && c.compare(dest[j-1], 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, c);
mergeSort(dest, src, mid, high, -off, c);
if (c.compare(src[mid-1], src[mid]) <= 0) {
System.arraycopy(src, low, dest, destLow, length);
return;
}
for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
}
可以发现算法和上一个方法完全一样,的区别是排序时使用的compare变成了传入的comparator了,其余的没有任何区别。
大概清楚了,此时发现java提供的排序还是比较高效的,大多数情况下你不需要自己去写排序算法,后我们再看看TreeSet中的在add的时候如何实现排序的,也是分别传入了comparator和没有传入,我们跟着源码里面,可以看到传入了comparator将这个属性设置给了TreeSet里面定义的一个TreeeMap,而TreeMap中的一个属性设置了这个Comparator:
源码片段7:TreeSet以及TreeMap设置Comparator的构造方法
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<E,Object>(comparator));
}
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
当然没有传入这个Comparator的时候自然没有设置到TreeMap中了,那么我们来看看TreeMap的add方法:
源码片段8:TreeSet#add(E e)
public boolean add(E e) {
return m.put(e,PRESENT)==null;
}
这个m是什么呢?其实通过源码片段7可以看出,m是开始实例化的一个TreeMap,那么我们需要看TreeMap的put方法
代码片段9:TreeMap#put(K key , V value)
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
root = new Entry<K,V>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<K,V>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
这里判定了是否存在Comparator进行不同方式来写入不同的位置,并没有重载方法,所以实现上也不一定有什么非要如何做,只需要保证代码可读性很好好,一切为它服务,否则那些过多的设计是属于过度设计,当然并不是说代码设计不重要,但是这些需要适可而止;另外TreeSet里面对于其他的方法也会做排序处理,我们这里仅仅是用add方法来做一个例子而已。
相信你对java的排序有了一些了解,也许本文说了一堆废话,因为本文不是在说排序算法,我们只是告诉你java是如何排序的,你在大部分情况下无需自己写排序算法来完成排序导致一些不必要的bug,而且效率未必有java本身提供的排序算法高效。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
Java性能测试有哪些不为众人所知的原则?Java设计模式??装饰者模式谈谈Java中遍历Map的几种方法Java Web入门必知你需要理解的Java反射机制知识总结编写更好的Java单元测试的7个技巧编程常用的几种时间戳转换(java .net 数据库)适合Java开发者学习的Python入门教程Java webdriver如何获取浏览器新窗口中的元素?Java重写与重载(区别与用途)Java变量的分类与初始化JavaScript有这几种测试分类Java有哪四个核心技术?给 Java开发者的10个大数据工具和框架Java中几个常用设计模式汇总java生态圈常用技术框架、开源中间件,系统架构及经典案例等
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南