JAVA之Arrays类定制排序、自己实现定制排序、Treeset、Treemap有序集合

JAVA之定制排序Comparator接口

前言

JAVA之Arrays类定制排序、自己实现定制排序、Treeset、Treemap有序集合

Array类是JAVA内置的一个类,如其名主要是针对数组,提供了一系列的静态方法,此处我们主要记录一下关于sort函数的定制排序。


一、Arrays类sort方法自然排序

1.Arrays类

Arrays 类是 Java 标准库中的一个实用类,它位于 java.util 包中,提供了大量用于操作数组的静态方法。这些方法涵盖了数组的常见操作,例如排序、搜索、比较、填充、转换等。

2.Arrays.sort(array)方法

该类的sort方法是对数组进行排序,类里面,对该方法进行了重写,一种是只有数组,没有参数的方法,如下面的代码所示。另一种是传入匿名内部类的方法。

代码如下(示例):

package com.cgq.socket;
import java.util.Arrays;
/**
 * @author 陈小陈
 * @vesion 1.0
 */
public class test {
    public static void main(String[] args) {
        Integer arr[] = {1,-1,7,0,89};
        Arrays.sort(arr);
        for (Integer i : arr){
            System.out.print(i+" ");
        }
    }
}

在这里插入图片描述
自然排序比较简单,直接调用Arrays的静态方法,传入数组类型的参数,可以根据输出结果看出来,这个函数是对数组本身进行排序,而并非创造一个新的数组。

3.Arrays.sort(array, Comparator)定制排序(源码剖析)

我们先看sort的源码:

public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a);
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }

sort传入的形参是一个Object的数组,然后有个if-else语句。我们继续看关于LegacyMergeSort的代码。

static final class LegacyMergeSort {
        private static final boolean userRequested =
            java.security.AccessController.doPrivileged(
                new sun.security.action.GetBooleanAction(
                    "java.util.Arrays.useLegacyMergeSort")).booleanValue();
    }

关于if语句后执行的是旧版的合并排序(legacy merge sort)。在某些情况下,用户可能会通过系统属性或者其他方式请求使用老的排序算法。这是为了兼容某些情况下老版本的行为。
假设你确实需要使用旧的合并排序算法,你可以通过设置系统属性来请求使用 legacyMergeSort:

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");

接下来是定制排序相关的内容;

public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

我们先来看一下Comparator是什么,根据源码可以看Comparator是一个接口。而且该接口有一个compare方法,没有方法体,只定下了两个参数。
在这里插入图片描述
在这里插入图片描述
我们通过匿名内部类的方法,实现这个接口,传入一个具体的对象。

package com.cgq.socket;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * @author 陈小陈
 * @vesion 1.0
 */
public class test {
    public static void main(String[] args) {
        Integer arr[] = {1,-1,7,0,89};
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        for (Integer i : arr){
            System.out.print(i+" ");   
        }
    }
}

这样就实现了从大到小的排序。
在这里插入图片描述
关于为什么是从大到小的,我们需要继续看源码,前面看到了调用private static void binarySort(T[] a, int lo, int hi, int start,Comparator<? super T> c)这个方法,该方法是Java 中使用的底层排序算法,是一种归并排序和插入排序的混合体,能够高效处理各种数据分布的场景。当排小数组时直接用简单的插入排序,大数组则是归并排序和插入排序的结合体。
这里的排序都是插入排序实现的,这里的插入排序又结合了二分查找,核心代码如下。其中c是Comparator接口对应的变量,动态绑定,调用其运行类型,也就是我们传入的compare方法。
就是这行代码if (c.compare(pivot, a[mid]) < 0)决定了我们从高到低还是从低到高进行排序。pivot是要插入的数o1,a[mid]o2,如果我们return的是o2-o1。这里举一个例子吧,数组有两个数,第一个为8,第二个为10。
下面代码a[2],lo=0,hi=1,start = 0。start是插入的起始,从lo到hi都要插入,通过for循环实现的。start++后,start=1,mid=left=right=0。o1 = pivot=a[start] = 10,o2 = a[mid]=8,return o2-o1=-2<0,执行if语句下面代码right=mid=0, n = start -left = 1,n是需要后移的数组数量,如果变成return o1-o2就会有完全不同的效果了。

 private static <T> void binarySort(T[] a, int lo, int hi, int start,
                                       Comparator<? super T> c) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
            T pivot = a[start];  //从statr等于0开始插入

            // Set left (and right) to the index where a[start] (pivot) belongs
            int left = lo;
            int right = start;
            assert left <= right;
            /*
             * Invariants:
             *   pivot >= all in [lo, left).
             *   pivot <  all in [right, start).
             */
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (c.compare(pivot, a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;

            /*
             * The invariants still hold: pivot >= all in [lo, left) and
             * pivot < all in [left, start), so pivot belongs at left.  Note
             * that if there are elements equal to pivot, left points to the
             * first slot after them -- that's why this sort is stable.
             * Slide elements over to make room for pivot.
             */
            int n = start - left;  // The number of elements to move
            // Switch is just an optimization for arraycopy in default case
            switch (n) {
                case 2:  a[left + 2] = a[left + 1];
                case 1:  a[left + 1] = a[left];
                         break;
                default: System.arraycopy(a, left, a, left + 1, n);
            }
            a[left] = pivot;
        }
    }
   

3.自己实现一下定制排序

package com.cgq.socket;
import java.util.Comparator;


/**
 * @author 陈小陈
 * @vesion 1.0
 */
public class test {
    public static void main(String[] args) {
        person[] p = new person[4];
        p[0] = new person("bob",44);
        p[1] = new person("Thrump",49);
        p[2] = new person("david",22);
        p[3] = new person("john",34);
        for(person a :p){
            System.out.print(a);
        }
        System.out.println();
        bubble_age(p, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                person p1 =(person) o1;
                person p2 =(person) o2;
                return p1.name.length() - p2.name.length();
            }
        });
        for(person a :p){
            System.out.print(a);
        }
        System.out.println();
        bubble_age(p, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                person p1 =(person) o1;
                person p2 =(person) o2;
                return p2.age - p1.age;
            }
        });
        for(person a :p){
            System.out.print(a);
        }
    }

//普通版本的排序
    public static void bubble_age(person[] p){
        person temp = null;
        for(int i = 0; i < p.length-1;i++){
            for(int j = 0;j < p.length- i - 1;j++){
                if(p[j].age > p[j+1].age){
                    temp = p[j];
                    p[j] = p[j+1];
                    p[j+1] = temp;
                }
            }
        }
    }
//冒泡定制一下
    public static void bubble_age(person[] p , Comparator c){
        person temp = null;
        for(int i = 0; i < p.length - 1;i++){
            for(int j = 0;j < p.length- i - 1;j++){
                if(c.compare(p[j],p[j+1])>0){
                    temp = p[j];
                    p[j] = p[j+1];
                    p[j+1] = temp;
                }
            }
        }
    }
}

//对一个类进行排序
class person{
    String name;
    int age;
    public person(String name, int age){    //构造器
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {  //重写toString
        return this.name + "  " +this.age+"  ";
    }
}

未排序前及两次排序后的结果如下图所示。
在这里插入图片描述
当然一般情况下,我们调用Arrays.sort()简单省事。

二、Treeset和Treemap

Java里常用的集合有List,可重复有顺序,还有set和map,map集合存放键值对K-V,而set把V设置成常量,只有变换的key,常用的Hashmap和Hashset都是无序的,都是利用哈希表+链表+红黑树实现的。而利用Tree实现的set和map可以保证有序性。这里利用了二叉排序树的性质再结合Comparator接口实现的。

这里举一个set的例子即可,map也是对key排序。

package com.cgq.socket;
package com.cgq.socket;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * @author 陈小陈
 * @vesion 1.0
 */
public class tttt {
    public static void main(String[] args) {
        TreeSet t1 = new TreeSet(); //调用无参构造函数
        TreeSet t2 = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length() - ((String)o2).length();
            }
        });
        t1.add("John");
        t1.add("bob");
        t1.add("david");
        t1.add("aaaaaaaaa");
        System.out.println(" t1 "+t1);
        t2.add("John");
        t2.add("bob");
        t2.add("david");
        t2.add("aaaaaaaaa");
        System.out.println(" t2 "+t2);
    }
}

在这里插入图片描述
我们可以通过上面的输出结果看出,如果调用无参构造的TreeSet是完全无序的,我们需要调用重载的另一种构造函数。

在这里插入图片描述
在这里插入图片描述
这里可以发现Comparator接口,下面是add函数上的核心代码。可以看出通过,Comparator接口接收到的对象调用compare方法的返回值,来确定排序,然后放到左子树和右子树。感兴趣的话可以自己追一下源码。
在这里插入图片描述

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值