• 源码分析二(ArrayList与LinkedList的区别)


    一:首先看一下ArrayList类的结构体系:

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    

    继承AbstractList抽象类,实现List接口、序列化接口等。

    它的底层维护了一个Object[]数组,或者可以说它的底层数据结构是数组

    1 /**
    2      * The array buffer into which the elements of the ArrayList are stored.
    3      * The capacity of the ArrayList is the length of this array buffer.
    4      */
    5     private transient Object[] elementData;

    而LinkedList结构体系:

    1 public class LinkedList<E>
    2     extends AbstractSequentialList<E>
    3     implements List<E>, Deque<E>, Cloneable, java.io.Serializable

    也是实现了List接口,但是它的底层数据结构表示数组,而是链式结构,内部维护一个相当于指针的Entry类

    1 private transient Entry<E> header = new Entry<E>(null, null, null);
    2     private transient int size = 0;
     1  private static class Entry<E> {
     2     E element;
     3     Entry<E> next;
     4     Entry<E> previous;
     5 
     6     Entry(E element, Entry<E> next, Entry<E> previous) {
     7         this.element = element;
     8         this.next = next;
     9         this.previous = previous;
    10     }
    11     }

    这里暂且称呼它为指针,这个指针维护了三个成员变量,第一个是元素、后面两个是当前元素的前一个指针和后一个指针。

    二:ArrayList读取数据效率高于LinkedList

    直接上源码:

    1  public E get(int index) {
    2     RangeCheck(index);
    3 
    4     return (E) elementData[index];
    5     }

    很简单,第一个校验参数,第二步根据索引直接到数组中查询元素返回,所以ArrayList读取数据的效率是固定的

    校验参数的源码也贴一下,如下:

    1 private void RangeCheck(int index) {
    2     if (index >= size)
    3         throw new IndexOutOfBoundsException(
    4         "Index: "+index+", Size: "+size);
    5     }

    如果指定索引的值大于等于集合的长度,那么会抛出角标越界异常。(注意:数组的最后一个元素index=size-1)

    而LinkedList读取元素的方式:

    1 public E get(int index) {
    2         return entry(index).element;
    3     }

    根据指定index获取指针,因为指针中维护元素信息,那么咱们就来看一下指针的获取方式:

     1  private Entry<E> entry(int index) {
     2         if (index < 0 || index >= size)
     3             throw new IndexOutOfBoundsException("Index: "+index+
     4                                                 ", Size: "+size);
     5         Entry<E> e = header;
     6         if (index < (size >> 1)) {
     7             for (int i = 0; i <= index; i++)
     8                 e = e.next;
     9         } else {
    10             for (int i = size; i > index; i--)
    11                 e = e.previous;
    12         }
    13         return e;
    14     }

    首先第一步还是参数校验,如果索引小于0或者大于等于size,抛空指针异常,然后从header指针开始寻找目标

    这里注意一下,为了提高查找效率,有个判断size>>1 就是右移两位(除以2),意思就是索引的位置如果在前半段

    就从前往后找,如果index的位置是后半段,就从后往前找,直到找到index索引位置结束,返回指针。因为指针中

    只维护了它的前一个指针和后一个指针,所以只能一个一个的往前(后)找,不能绕过,所以效率自然就低于ArrayList。

    三:ArrayList操作数据(增加或者删除)效率低于LinkedList

    首先看ArrayList的add():

    1  public boolean add(E e) {
    2     ensureCapacity(size + 1);  // Increments modCount!!
    3     elementData[size++] = e;
    4     return true;
    5     }

    如果仅仅只是add,那么很简单,第一步扩容,然后将新增的元素放到最后一个元素的后面,返回true。

    但是如果将元素插入到指定index位置时,就有些麻烦了:

     1   public void add(int index, E element) {
     2     if (index > size || index < 0)
     3         throw new IndexOutOfBoundsException(
     4         "Index: "+index+", Size: "+size);
     5 
     6     ensureCapacity(size+1);  // Increments modCount!!
     7     System.arraycopy(elementData, index, elementData, index + 1,
     8              size - index);
     9     elementData[index] = element;
    10     size++;
    11     }

    第一步还是参数校验,索引位置不能小于0或者大于集合中元素数量,第二步扩容,然后将index位置包括以后元素

    整体向后移动一个位,这样就把数组中index位置给空出来了,于是将element放到index为,集合中元素数量加一,

    这里有移动数据的操作,所以效率比较低。

    再来看一看ArrayList的remove操作,与add(int index,E element)的操作大致类似:

     1  public E remove(int index) {
     2     RangeCheck(index);
     3 
     4     modCount++;
     5     E oldValue = (E) elementData[index];
     6 
     7     int numMoved = size - index - 1;
     8     if (numMoved > 0)
     9         System.arraycopy(elementData, index+1, elementData, index,
    10                  numMoved);
    11     elementData[--size] = null; // Let gc do its work
    12 
    13     return oldValue;
    14     }

    第一步参数校验,index不能超过size,第二步获取index位置元素,暂且存储,然后将index+1以及后面的元素整体

    向前移动一位,这样index位置的元素就被index+1给替换了,而size-1位置元素和size-2相等,需要置为null,等待垃圾回收器处理,

    然后将index位置元素返回,这个过程又涉及到移动数组中的数据,所以效率比较低。

    来看看LinkedList是怎么处理插入和删除的:

    1  public void add(int index, E element) {
    2         addBefore(element, (index==size ? header : entry(index)));
    3     }

    add(int index,E element)调用了addBefore这个方法,如果index==size,那么指针就是head,如果不是就会根据index

    查找对应的指针,意思就是要将元素插入到哪个位置。

    1 private Entry<E> addBefore(E e, Entry<E> entry) {
    2     Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
    3     newEntry.previous.next = newEntry;
    4     newEntry.next.previous = newEntry;
    5     size++;
    6     modCount++;
    7     return newEntry;
    8     }

    首先创建并初始化指针newEntry,将元素之e,以及它的前后指针维护到创建的指针对象中,然后与前后指针连接,就

    相当于握手,集合中元素数量size自增,将指针元素返回。过程很简单,没有涉及到移动数据,只是将新增的元素与前后

    元素握手就可以了,所以效率高于ArrayList。

    再来看一看删除操作:

    1  public E remove(int index) {
    2         return remove(entry(index));
    3     }

    remove(int index)有个重载的方法入参为指针Entry,方法如下:

     1 private E remove(Entry<E> e) {
     2     if (e == header)
     3         throw new NoSuchElementException();
     4 
     5         E result = e.element;
     6     e.previous.next = e.next;
     7     e.next.previous = e.previous;
     8         e.next = e.previous = null;
     9         e.element = null;
    10     size--;
    11     modCount++;
    12         return result;
    13     }

    首先暂存要删除的元素,然后将元素的后一个指针与前一个指针握手,再将该指针维护的前后位置元素置空,

    然后返回要删除的元素,完成操作,过程没有涉及数据的移动,所以效率高于ArrayList。

  • 相关阅读:
    VC++学习(16):线程同步与异步套接字
    VC++学习(15):多线程
    VC++学习(12):文件操作
    VC++学习(10):绘图控制
    VC++学习(13):文档串行化
    VC++学习(11):图形的保存和重绘
    VC++学习(18):Active控件
    四大数据库的比较(SQL Server、Oracle、Sybase和DB2)
    Gridview中二级联动
    VS 2008中PDA开发环境的相关配置
  • 原文地址:https://www.cnblogs.com/warrior4236/p/6561796.html
Copyright © 2020-2023  润新知