Collections类中的shuffle方法源码分析

Collections类中的shuffle方法源码分析

shuffle方法可以将List中的数据随机打乱顺序,之前我们就使用了这个方法实现了扑克牌的洗牌功能,下面来看下shuffle方法的源码:

public static void shuffle(List<?> list) {
    Random rnd = r;
    if (rnd == null)
        r = rnd = new Random(); // harmless race.
    shuffle(list, rnd);
}

该方法中获取了一个随机数的对象,之后调用了与其构成重载的另一个shuffle方法:

 @SuppressWarnings({"rawtypes", "unchecked"})
public static void shuffle(List<?> list, Random rnd) {
    int size = list.size();
    if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
        for (int i=size; i>1; i--)
            swap(list, i-1, rnd.nextInt(i));
    } else {
        Object arr[] = list.toArray();

        // Shuffle array
        for (int i=size; i>1; i--)
            swap(arr, i-1, rnd.nextInt(i));

        // Dump array back into list
        // instead of using a raw type here, it's possible to capture
        // the wildcard but it will require a call to a supplementary
        // private method
        ListIterator it = list.listIterator();
        for (int i=0; i<arr.length; i++) {
            it.next();
            it.set(arr[i]);
        }
    }
}

上面的shuffle方法有个逻辑判断,第一个判断是当List对象的长度小于5或者List对象是RandomAccess实例的时候直接执行了for循环,在这个for循环中调用了swap方法:

@SuppressWarnings({"rawtypes", "unchecked"})
public static void swap(List<?> list, int i, int j) {
    // instead of using a raw type here, it's possible to capture
    // the wildcard but it will require a call to a supplementary
    // private method
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

在这个swap方法中可以看到,
swap方法中接收的参数j是一个随机数,将这个随机数j作为索引位置,直接调用了list中的set方法来修改list中的数据。这个其实就相当于是使用了基本的for循环和随机数来实现的随机打乱list中的数据顺序。

再回到shuffle方法,在逻辑判断中还有一个else,在这个里面的做法是先将list转换为数组,之后使用for循环和随机数来随机打乱该数组的顺序,最后使用了迭代器来将这个数组中的数据放入到list中,这样就相对于随机的改变了list中的数据顺序。

        //将list转换为数组
        Object arr[] = list.toArray();

        // 随机打乱数组中的顺序
        for (int i=size; i>1; i--)
            swap(arr, i-1, rnd.nextInt(i));

        //使用迭代器将数组中的数据放入到list集合中
        ListIterator it = list.listIterator();
        for (int i=0; i<arr.length; i++) {
            it.next();
            it.set(arr[i]);
        }

以上就是shuffle方法的源码简要分析。这里有一个问题,就是为什么在shuffle方法中要有一个逻辑判断呢?直接修改list中数据的顺序不就行了吗。这个就跟list有关系了,List只是一个接口,有一些他的实现类,最具代表性的是ArrayList和LinkedList。
当调用shuffle方法时传入的是ArrayList的话,因为ArrayList实现了RandomAccess接口,那么就会执行if中的代码,这里面的做法是使用一个基本for循环直接修改list中的数据。
当调用shuffle方法时传入的是LinkedList的话,就会执行else中的代码,他在里面先将其转换为数组,然后再将数组中的元素使用迭代器再重新放入到LinkedList中。
在使用基本for循环对ArrayList和LinkedList进行遍历的时候,ArrayList的运行效率要远比LinkedList高,这是因为ArrayList底层使用数组实现,所以通过下标访问的话ArrayList效率高。
当时用迭代器或者增强for循环(底层就是使用的迭代器)遍历ArrayList和LinkedList时,因为LinkedList底层使用链表实现,所以LinkedList的效率远比ArrayList高。
由于以上的原因,为了程序的效率,所以在shuffle方法中使用了逻辑判断来区分具体是哪一种List从而高效的对其进行遍历。