Collections.reverse 代码思考-超越昨天的自己系列(13)

点进Collections.reverse的代码瞄了眼,然后就开始了一些基础知识的收集。

现在发现知道的越多,知道不知道的越多。

列几个记录下:

reverse方法源码:

 
/**
     * Reverses the order of the elements in the specified list.<p>
     *
     * This method runs in linear time.
     *
     * @param  list the list whose elements are to be reversed.
     * @throws UnsupportedOperationException if the specified list or
     *         its list -iterator does not support the <tt>set</tt> operation.
     */
    public static void reverse (List<?> list) {
        int size = list.size();
        if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
            for ( int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
                swap(list, i, j);
        } else {
            ListIterator fwd = list.listIterator();
            ListIterator rev = list.listIterator(size);
            for ( int i=0, mid=list.size()>>1; i<mid; i++) {
           Object tmp = fwd.next();
                fwd.set(rev.previous());
                rev.set(tmp);
            }
        }
    }

 

1,首先看见RandomAccess

List 实现所使用的标记接口,用来表明其支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。

将操作随机访问列表的最佳算法(如 ArrayList)应用到连续访问列表(如 LinkedList)时,可产生二次项的行为。如果将某个算法应用到连续访问列表,那么在应用可能提供较差性能的算法前,鼓励使用一般的列表算法检查给定列表是否为此接口的一个 instanceof,如果需要保证可接受的性能,还可以更改其行为。

现在已经认识到,随机和连续访问之间的区别通常是模糊的。例如,如果列表很大时,某些 List 实现提供渐进的线性访问时间,但实际上是固定的访问时间。这样的 List 实现通常应该实现此接口。实际经验证明,如果是下列情况,则 List 实现应该实现此接口,即对于典型的类实例而言,此循环:

   for (int i=0, n=list.size(); i < n; i++)
         list.get(i);
 

的运行速度要快于以下循环:

     
for (Iterator i=list.iterator(); i.hasNext(); )
         i.next();

 

2,标记接口(marker interface

  又叫Tagging Interfaces。标识接口是没有任何方法和属性的接口。标识接口不对实现它的类有任何语义上的要求,它仅仅表明实现它的类属于一个特定的类型。常见的有Serializable  Cloneable    Remote    EventListener 
你当然可以任意定义没有任何方法和属性的接口,但肯定不应该称为标识接口,因为JDK里的“标识接口”不光是“只有个名字”这么简单,更重要的是,实现这些标志接口的类,确实多了功能,尽管你看不到这些功能是怎么实现的。比如,Serializable,实现了这个接口,那这个序列化的工作,到底是谁做的那?Cloneable,实现了这个接口,并在重写的clone()方法里只是调用了一下super.clone(),就产生了一个全新的对象,要知道Object里的clone()方法是没有任何实现的,这个克隆的工作,到底是谁完成的那?JVM or Reflection,但是你看不到它们。 
拿java.io.Serializable接口作为例子来说明一下。 如果存在一个对象,它实现了java.io.Serializable接口,由于接口本身没有定义任何方法行为。所以实现接口的行为由java编译器来完成。当一个java类实现了这个接口,在编译过程中,java编译器会发现这个类的对象是属于java.io.Serializable这种类型,那么编译器就会为这个特殊的类实现序列化所要求的特殊的行为,使得该类的对象可以在不同虚拟机之间传递。 所以说,我们需要有一个标记的东西 来通知java编译器这个特殊的属性,我们就定义了标识接口。
关于标志接口的对于错,争论是有的: 
标志接口是对接口的误用,应该被避免,使用标志接口的类,都是一些相当古老的类。Java 5 加入 注解 特性后,标志接口更不会再有出现的必要。 
使用注解来标识类,方法等的特定标签更加灵活,这又是一个可以扩展学习的点。
 
3,Iterator 和 ListIterator
想到个问题,比如list个通过get获取其中元素,为什么要有迭代器呢?
Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。
 

例如,如果没有使用Iterator,遍历一个数组的方法是使用索引:
        for(int i=0; i<array.size(); i++) { ... get(i) ... } 
    客户端都必须事先知道集合的内部结构,访问代码和集合本身是紧耦合,无法将访问逻辑从集合类和客户端代码中分离出来,每一种集合对应一种遍历方法,客户端代码无法复用。
  更恐怖的是,如果以后需要把ArrayList更换为LinkedList,则原来的客户端代码必须全部重写。
为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合:

         for(Iterator it = c.iterater(); it.hasNext(); ) { ... }

  奥秘在于客户端自身不维护遍历集合的"指针",所有的内部状态(如当前元素位置,是否有下一个元素)都由Iterator来维护,而这个Iterator由集合类通过工厂方法生成,因此,它知道如何遍历整个集合。

  客户端从不直接和集合类打交道,它总是控制Iterator,向它发送"向前","向后","取当前元素"的命令,就可以间接遍历整个集合
 

ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator 没有此功能。

四、都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。
 
测试:
尝试用ArrayList 和 linkedList 来使用两种方式进行翻转操作:
一种操作是使用源码中swap的方式,一种使用ListInterator。
第一个测试结果:结果1:7656 结果2:2
可见像linkedList 这种是不肯能使用swap方式去翻转的,代码中也做了处理。链表,在使用随机访问每次耗时太长,导致这种结果。
public static void main(String[] args) {
        List list =new LinkedList();
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
        int size = list.size();
        long t1 = System.currentTimeMillis();
        for ( int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
            swap(list, i, j);
        long t2 = System.currentTimeMillis();
        System.out.println(t2 - t1);//结果1
        long t3 = System.currentTimeMillis();
        ListIterator fwd = list.listIterator();
        ListIterator rev = list.listIterator(size);
        for ( int i=0, mid=list.size()>>1; i<mid; i++) {
            Object tmp = fwd.next();
            fwd.set(rev.previous());
            rev.set(tmp);
        }
        
        long t4 = System.currentTimeMillis();
        System.out.println(t4 - t3);//结果2
    }
    
    public static void swap(List<?> list, int i, int j) {
        final List l = list;
        l.set(i, l.set(j, l.get(i)));
    }

那么ArrayList 使用这两种方式的效果呢?

测试结果相差无几,随着增大数据量,swap要好于ListInterator,但是有时微乎其微,所以这个reverse代码中并没有对大数据量的ArrayList进行swap方式,减少了代码冗余,也没有降低什么性能。

 

 

 

---------------------------------------

还有很多扩展学习的地方,继续前进吧。

 

posted on 2015-11-03 11:23  每当变幻时  阅读(2923)  评论(0编辑  收藏  举报

导航