ArrayList与LinkedList源码解析

                                      ArrayList与LinkedList源码解析

     相信使用java的同仁对这个都不陌生, 在java中我们最长使用的数据结构就是List与Map了, 而今天我们就来看看List中最常用的两种实现。

     1. 首先我们来看ArrayList集合, 他的类层级图如下:

                       

       1). 首先我们需要了解ArrayList的数据结构模型为:

                    struct  ArrayList {

            static final int DEFAULT_CAPACITY = 10;
                           static final Object [] EMPTY_ELEMENTDATA = {} ;
            static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
                           Object [] elementData ;     

                           int size;

                     }

            从这个结构中我们可以知道ArrayList的结构是以数组的形式进行操作的。

        2). 然后我们一般就需要关心数据结构的增删查改操作了, 数据结构主要的操作就是增删查改。

            (1). 首先看一下ArrayList的add操作:

                   

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
                    这段代码的增加操作和普通的数据操作一样, 关键在于ensureCapacityinternal()这个方法, 该方法会在ArrayList长度不够时进行对应的扩容,具体源代码如下:

          

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

                   在我们进入ensureCapacityInternal()方法去时,我们最后发现grow()才是真正进行操作的方法从这上面来看, 我们到一般情况下扩容的时候计算方法为增加原有数组的1/2的长度。  而且我们从第二个If中我们可以知道ArrayList的限制为Integer的最大值2147483647,当然我们的对象不可能这么多, 不然服务器不爆都难。然后使用Arrays.copyOf来进行扩容。

            (2).其他的删改查都很简单, 这里就不进行介绍了。

      2. LinkedList的类层次结构图:

                        

        

                          

               从这个图中我们知道,ArrayList和LinkedList都继承了最左边这个分支类层级, 他们中有些通用的方法都来自这个实现结构。

          1). 首先来了解LinkedList的数据结构:

                   struct   Node<E> {

             E item;
             Node<E> next;
             Node<E> prev;

                   }

                   struct   LinkedList {

            transient int size = 0;      
            transient Node<E> first;
            transient Node<E> last;

                   }

         从这个结构中我们可以知道, LinkedList使用的是双向链表来进行数据存储的。

           2). 现在我们来看看它的相关操作

                    (1).  add操作

      

public boolean add(E e) {
        linkLast(e);
        return true;
    }

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
                     这段代码就是LinkedList的操作, 增加一个新节点, 就是追加该节点到链表的最后。 从代码来看, LinkedList的长度是没有限制的。对比ArrayList与LinkedList的add方法发现: 当使用ArrayList新增数据时, 我们要进行对应的数据移动工作, 它会先创建一个扩容后的数组, 将原数组数据拷贝到新数据中; 而我们在使用LinkedList的时候,则是直接在链表尾部追加新元素。 很明显, LinkedList的add操作比ArrayList的操作要便利很多, 性能更加。所以当我们是List时, 如果该List有频繁的增删改时, 推荐使用LinkedList操作。


                     (2). get操作

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
                              这就是它的get操作, 从该操作来看, 它先看当前索引是靠近头部还是尾部, 然后进行链表的遍历进行查找。  从LinkedList和ArrayList的get中我们可以知道,由于ArrayList采用数据方式存储数据, 该种存储是连续的,所以查找数据时不需要遍历数组就可以根据下标获得对应数据, 而LinkedList则是使用链表, 该种方式存储数据不是连续的, 当我们需要查找数据时, 需要对整个链表进行遍历; 可以知道, 当数据靠近链表中部时, 我们最快查找也需要size/2次才能查到。 所以当我们在使用List进行数据存储,更多的操作是查找的时候, 推荐使用ArrayList。
                      (3). remove操作:

public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
                         上面的代码时根据对象值进行删除的, 因为根据索引删除的基本就是unlink在操作, 所以没有贴出代码, 从上面可以看到当使用对象值进行删除时, 所有的值相同的对象都会被删除; 而我们使用索引删除的时候, 只是删除对应索引位置的对象。在这点上, ArrayList和LinkedList的做法也是一样的。

                    (3). set操作: 我们想替换一个对象的时候使用的是set方法进行操作。

public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

                           set操作通过node方式找到对应的节点, 然后替换调值即可。

                     (4). contains操作:

public boolean contains(Object o) {
        return indexOf(o) != -1;
    }
public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }

                                为什么在这里要讲contains方法, 当我们在使用List并且想去重的时候, 我们就可以使用这个方法, 但是我们观察一下当前这个方法, 它会找到第一个值相等的元素并且返回对应的索引下标, 而在它进行对比是,它调用了当前对象的equals方法, 这个方法是关键。 所以在我们需要使用List的contains的时候, 我们需要重写当前对象对应类的equals方法, 让对应的比较按我们的需求进行。

总结:       在介绍对应源码时, 主要介绍了LinkedList, 这里并不是说它更重要, 而是因为ArrayList的是采用数组进行保存的, 对于数据的操作相比开发人员都很清楚, 所以就集中精力分析了下LinkedList。 从这些分析中知道:

        1. ArrayList采用的是数组方式进行数据保存, 而LinkedList采用的是双向链表进行保存。

        2. 从它们的操作实现中, 我们可以知道在查询比较多的情况下使用ArrayList, 增删改频繁的时候使用LinkedList是比较不错的选择。

        3. 在我们使用pojo时, 最好别忘记了重写equals方法, 防止以后比较会使用到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值