• 重新开始学习javase_集合_List


    一,List之ArrayList(转:http://blog.csdn.net/zheng0518/article/details/42198205)

    1. ArrayList概述:

       ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
       每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。 
       注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。

    2. ArrayList的实现:

       对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。下面我们来分析ArrayList的源代码:

       1) 底层使用数组实现:

    private transient Object[] elementData;

     2) 构造方法: 
       ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。

    public ArrayList() {  
        this(10);  
    }  
      
    public ArrayList(int initialCapacity) {  
        super();  
        if (initialCapacity < 0)  
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);  
        this.elementData = new Object[initialCapacity];  
    }  
      
    public ArrayList(Collection<? extends E> c) {  
        elementData = c.toArray();  
        size = elementData.length;  
        // c.toArray might (incorrectly) not return Object[] (see 6260652)  
        if (elementData.getClass() != Object[].class)  
            elementData = Arrays.copyOf(elementData, size, Object[].class);  
    }

    3) 存储: 
       ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法。下面我们一一讲解

    // set(int index,E element)用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。  
    public E set(int index, E element) {  
        RangeCheck(index);  //检查范围
      
        E oldValue = (E) elementData[index];  //把指定位置上的元素取出来作为返回
        elementData[index] = element;  //把指定位置上的元素的值设置为指定值
        return oldValue;  
    }
    // add(E e)将指定的元素添加到此列表的尾部。  
    public boolean add(E e) {  
        ensureCapacity(size + 1);   //把底层数组的长度增加1
        elementData[size++] = e;  //再最后位置加上指定值
        return true;  
    }
    // add(int index,E element)将指定的元素插入此列表中的指定位置。  
    // 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。  
    public void add(int index, E element) {  
        if (index > size || index < 0)  
            throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);  
        // 如果数组长度不足,将进行扩容。  
        ensureCapacity(size+1);  // Increments modCount!!  
        // 将 elementData中从Index位置开始、长度为size-index的元素,  
        // 拷贝到从下标为index+1位置开始的新的elementData数组中。  
        // 即将当前位于该位置的元素以及所有后续元素右移一个位置。  
        System.arraycopy(elementData, index, elementData, index + 1, size - index);  
        elementData[index] = element;  
        size++;  
    }
    //addAll(conllection<? extends E> c 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。  
    public boolean addAll(Collection<? extends E> c) {  
        Object[] a = c.toArray();  //集合转数组
        int numNew = a.length;  //数组的长度
        ensureCapacity(size + numNew);  // 扩容  
        System.arraycopy(a, 0, elementData, size, numNew);  
        size += numNew;  
        return numNew != 0;  
    }
    // addAll(int index,Collection<? extends E> c)从指定的位置开始,将指定collection中的所有元素插入到此列表中。  
    public boolean addAll(int index, Collection<? extends E> c) {  
        if (index > size || index < 0)  
            throw new IndexOutOfBoundsException(  
                "Index: " + index + ", Size: " + size);  
      
        Object[] a = c.toArray();  
        int numNew = a.length;  
        ensureCapacity(size + numNew);  // Increments modCount  
      
        int numMoved = size - index;  
        if (numMoved > 0)  
            System.arraycopy(elementData, index, elementData, index + numNew, numMoved);  
      
        System.arraycopy(a, 0, elementData, index, numNew);  
        size += numNew;  
        return numNew != 0;

    4) 读取:

    // 返回此列表中指定位置上的元素。  
    public E get(int index) {  
        RangeCheck(index);  
      
        return (E) elementData[index];  
    } 

      5) 删除: 
       ArrayList提供了根据下标或者指定对象两种方式的删除功能。如下:

    // 移除此列表中指定位置上的元素。  
    public E remove(int index) {  
        RangeCheck(index);  
      
        modCount++;  
        E oldValue = (E) elementData[index];  
      
        int numMoved = size - index - 1;  
        if (numMoved > 0)  
            System.arraycopy(elementData, index+1, elementData, index, numMoved);  
        elementData[--size] = null; // Let gc do its work  
      
        return oldValue;  
    }
    // 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。  
    public boolean remove(Object o) {  
        // 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。  
        if (o == null) {  
            for (int index = 0; index < size; index++)  
                if (elementData[index] == null) {  
                    // 类似remove(int index),移除列表中指定位置上的元素。  
                    fastRemove(index);  
                    return true;  
                }  
    } else {  
        for (int index = 0; index < size; index++)  
            if (o.equals(elementData[index])) {  
                fastRemove(index);  
                return true;  
            }  
        }  
        return false;  
    } 

     注意:从数组中移除元素的操作,也会导致被移除的元素以后的所有元素的向左移动一个位置。

     6) 调整数组容量: 
       从上面介绍的向ArrayList中存储元素的代码中,我们看到,每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。

    public void ensureCapacity(int minCapacity) {  
        modCount++;  
        int oldCapacity = elementData.length;  
        if (minCapacity > oldCapacity) {  
            Object oldData[] = elementData;  
            int newCapacity = (oldCapacity * 3)/2 + 1;  
                if (newCapacity < minCapacity)  
                    newCapacity = minCapacity;  
          // minCapacity is usually close to size, so this is a win:  
          elementData = Arrays.copyOf(elementData, newCapacity);  
        }  
    } 

    从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
       ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。代码如下:

    public void trimToSize() {  
        modCount++;  
        int oldCapacity = elementData.length;  
        if (size < oldCapacity) {  
            elementData = Arrays.copyOf(elementData, size);  
        }  
    }

    7) Fail-Fast机制: (http://blog.csdn.net/chenssy/article/details/38151189)
    ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

    二,List之LinkedList(转:http://blog.csdn.net/zheng0518/article/details/42198599)

    1. LinkedList概述:

    List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

    此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。

    所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。

    注意,此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示:

                        List list = Collections.synchronizedList(new LinkedList(...));

    此类的 iterator 和 listIterator 方法返回的迭代器是快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。

    注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。

    2. LinkedList的实现:

    Java代码  收藏代码
    private transient Entry<E> header = new Entry<E>(null, null, null);   

    这个成员变量是LinkedList的关键,它在链表中没有实际数据意义,是链表的标示(通俗一点就是链表的第一个无意义的元素),而且被修饰为transient,标示着他不会被序列化。header也可以当做队列末尾的元素,因为是双向列表,所以header.next末尾元素后边的元素就成了队首元素,header.previous就是队尾元素了,看一下它的添加方法

    Java代码  收藏代码
    public void addFirst(E paramE) {  
        addBefore(paramE, this.header.next);//队首  
    }  
    public void addLast(E paramE) {  
        addBefore(paramE, this.header);//队尾  
    }

     以上两个方法都利用addBefore方法将元素添加到指定对象之前,

     addFirst向队头加元素,将元素paramE添加到header.next-队首元素之前;

     addLast向队尾加元素,将元素paramE添加到header之前;

      再看一下addBefore(E e,Entry<E> entry)函数

    /*** 
     * 要添加的元素:paramE 
     * 目标对象:paramEntry 
     */  
    private Entry<E> addBefore(E paramE, Entry<E> paramEntry)  
    {  
        //要添加的对象  
        Entry localEntry = new Entry(paramE, paramEntry, paramEntry.previous);  
        /*** 
         * localEntry.previous = paramEntry.previous 
         * 目标对象的前一元素的后一元素(localEntry.previous.next)设置为要添加的对象 
         */  
        localEntry.previous.next = localEntry;  
        /*** 
         * localEntry.next = paramEntry 
         * 目标对象的前一元素(localEntry.next.previous)设置为要添加的对象 
         */  
        localEntry.next.previous = localEntry;  
        this.size += 1;  
        this.modCount += 1;  
        return localEntry;  
    }  

    链表的基本特性是插入速度快,遍历速度慢,下面两个方法可以反映这个特点

    Java代码  收藏代码

    public int indexOf(Object paramObject) {  
        int i = 0;  
        Entry localEntry;  
        /*** 
         * 遍历规则:从头到尾,序列呈升序状态 
         */  
        if (paramObject == null)  
            for (localEntry = this.header.next; localEntry != this.header; localEntry = localEntry.next) {  
                if (localEntry.element == null)  
                    return i;  
                i++;  
            }  
        else {  
            for (localEntry = this.header.next; localEntry != this.header; localEntry = localEntry.next) {  
                if (paramObject.equals(localEntry.element))  
                    return i;  
                i++;  
            }  
        }  
        return -1;  
    }  
    public int lastIndexOf(Object paramObject) {  
        int i = this.size;  
        Entry localEntry;  
        /*** 
         * 遍历规则:从尾到头,序列呈降序状态 
         */  
        if (paramObject == null) {  
            for (localEntry = this.header.previous; localEntry != this.header; localEntry = localEntry.previous) {  
                i--;  
                if (localEntry.element == null)  
                    return i;  
                }  
        }else {  
            for (localEntry = this.header.previous; localEntry != this.header; localEntry = localEntry.previous) {  
                i--;  
                if (paramObject.equals(localEntry.element))  
                    return i;  
            }  
        }  
        return -1;  
    }  

    值得注意的是,链表插入数据速度快的说法是相对的,在数据量很小的时候,ArrayList的插入速度不仅不比LinkedList慢,而且还快很多(本文不作介绍,读者可自行测试),只有当数据量达到一定量,这个特性才会体现出来,这需要开发者明确需求场景

    LinkedList的方法entry(int index)类似ArrayList的get(int index)


    Java代码  收藏代码

    /*** 
     * 根据序号获取Entry对象 
     */  
    private Entry<E> entry(int paramInt) {  
        if ((paramInt < 0) || (paramInt >= this.size)) {  
            throw new IndexOutOfBoundsException("Index: " + paramInt + ", Size: " + this.size);  
        }  
        Entry localEntry = this.header;  
        int i;  
        /*** 
         * 二分法:目标序号小于Size的1/2,则从头到尾 
         *             如果大于Size的1/2,则从尾到头 
         */  
        if (paramInt < this.size >> 1) {  
            for (i = 0; i <= paramInt; i++)  
                localEntry = localEntry.next;  
        } else {  
            for (i = this.size; i > paramInt; i--)  
                localEntry = localEntry.previous;  
        }  
        return localEntry;  
    }  

    4. 内部迭代器:ListItr

    虽然上层父类AbstractList<E>已经实现了迭代器,但LinkedList的直接父类AbstractSequentialList<E>给子类重新定义个一个需要实现的迭代器的抽象方法,代码如下:

    Java代码  收藏代码
    public abstract class AbstractSequentialList<E> extends AbstractList<E> {  
        /*** 
         * 返回子类实现的迭代器 
         */  
        public Iterator<E> iterator() {  
            return listIterator();  
        }  
        public abstract ListIterator<E> listIterator(int paramInt);  
    }  

    此处实现的迭代器内部机制跟AbstractList基本一致,可以看看源码

  • 相关阅读:
    学习进度14
    计算最长英语单词链
    梦断代码阅读笔记02
    梦断代码阅读笔记01
    学习进度13
    评价输入法
    课堂测试——找水王
    第二阶段冲刺05
    实验2
    实验 1 Linux 系统的安装和常用命令
  • 原文地址:https://www.cnblogs.com/wangyang108/p/5799031.html
Copyright © 2020-2023  润新知