• 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方式,减少了代码冗余,也没有降低什么性能。

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

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

  • 相关阅读:
    Java虚拟机(JVM)中的内存设置详解
    设置TOMCAT的JVM虚拟机内存大小
    什么是SQL注入式攻击
    常见的数据库基础面试题大全
    Struts1与Struts2的12点区别
    sql server导出insert语句
    jsp 将html字符串输出html标签
    GC 基础
    jstl fn标签
    Struts2 中result type属性说明
  • 原文地址:https://www.cnblogs.com/killbug/p/4932639.html
Copyright © 2020-2023  润新知