关于Arraylist和LinkedList

一,Arraylist 在我们开发中经常用到,今天就来研究一下它的实现原理,由源码可以看出,其实它内部维护了一个数组

 /**
  * Shared empty array instance used for empty instances.
  */
 private static final Object[] EMPTY_ELEMENTDATA = {};

    所以,它的增加,删除,修改,查询都是对这个数组的操作。

    1.下面来研究一下add方法:

  public boolean add(E e) {
      ensureCapacityInternal(size + 1);  // Increments modCount!!
      elementData[size++] = e;
      return true;
  }

   ensureCapacityInternal(size + 1);是对数组的容量进行检测和扩容

  private void ensureCapacityInternal(int minCapacity) {
     ...

    ensureExplicitCapacity(minCapacity);
  }

  private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    //如果要加入的位置大于数组的容量
    if (minCapacity - elementData.length > 0)
        //进行扩容操作
        grow(minCapacity);
  }
  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);
  }

int newCapacity = oldCapacity + (oldCapacity >> 1);由这里可以看出,其扩容规则是把原来容量再加上原来容量的一半。

顺便提了一下扩容规则,在进行容量检测完毕后调用了elementData[size++] = e;意思就是把新加进来的数据放到数组的最后一位。

2.把数据添加到指定位置add(int index,E e)

   把数据添加到指定位置的思路是把index后面的所有数据copy到index+1的位置,相当于把index后面的数据统一往后移一位,       然后再把新的数据添加到index的位置。

public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    //和add方法一样,首先先进行容量检测及扩容操作
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //和add方法的区别 把size - index(即index后面)的所有数据copy到数组的index + 1的位置。
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //把新数据放到index的位置
    elementData[index] = element;
    size++;
}

3.删除指定位置数据 remove(int index)

  原理和把数据添加到指定位置差不多,把index位置的数据删除,把index位置后面的所有数据往前移动一位

 public E remove(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    modCount++;
   //获取到index位置的数据
    E oldValue = (E) elementData[index];
   //index后面所有数据的开始下标
    int numMoved = size - index - 1;
   //如果要删除的不是最后一位数据
    if (numMoved > 0)
   //开始删除并移动数据
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //把数组的size-1并把最后一位置空
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
 }

4.删除指定数据 remove(Object object)

  思路就是通过for循环遍历数据,取出每一个值和object进行比较,如果匹配到要删除的数组则把index位置的数据删除,把           index位置后面的所有数据往前移动一位。

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        //通过for循环遍历数组
        for (int index = 0; index < size; index++)
            //比较是否是需要删除的数据
            if (o.equals(elementData[index])) {
                //匹配成功开始删除
                fastRemove(index);
                return true;
            }
    }
    return false;
}

/*
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
//这里和删除指定位置的数据操作一样
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)

        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

其它方法也都是基于对数组的操作,这里就不分析了,由于Arraylist是基于数组实现的,所以它的查询效率相对比较高,但是删除和添加效率会比较低,因为涉及到数据的复制移动。

二,分析完Arraylist再来看一下LinkedList吧

LinkedList和Arraylist不同,它是一个双向链表,里面有我们要存储的数据节点,每个节点有三个区域 《前指针域--数据域--后指针域》前指针域指向前一个节点,后指针域指向后一个节点,这样相邻的两个数据都相互存在指向。

 

如图,A B C代表三个节点,假如要删除b节点那么只需要把A节点的后指针域指向C,把C的前指针域指向A,那么B与A和C的连接将自动断开链表中就不再有B这个元素,明白了这个原理那添加也不难了,只是对指针指向进行操作。

下面来看一下源码:

 LinkedList会初始化第一个结点和最后一个结点,当添加或删除的时候会作为参照使用。

/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * Pointer to last node.
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

1. 从尾部添加

/**
 * Links e as last element.
 */
void linkLast(E e) {
    //拿到尾部的结点
    final Node<E> l = last;
    //创建一个新结点,并把它的前指针域设置为l,把它的后指针域设置为空
    final Node<E> newNode = new Node<>(l, e, null);
    //更新last为新的最后一个结点
    last = newNode;
     //如果l为空说明列表是空的,直接把新添加的结点置为first,不为空就把l(旧的尾结点)的后指针域指向新的尾结点。
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

2. 从中间添加

    首先看一下add方法

public void add(int index, E element) {
    checkPositionIndex(index);
    //这里会判断下传入的index,如果index==size说明可以直接调用linkLast方法加到尾部
    if (index == size)
        linkLast(element);
    else
       //这里通过node(index)方法拿到index位置的结点
        linkBefore(element, node(index));
}

       //否则才会执行从中间添加

/**
 * Inserts element e before non-null Node succ.
 */
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    //拿到index位置结点的前指针域指向
    final Node<E> pred = succ.prev;
    //新建结点,并把新结点的前指针域指向index结点的前指针域,把它的后指针域指向index结点。
    final Node<E> newNode = new Node<>(pred, e, succ);
    //把index结点的前指针域指向新结点
    succ.prev = newNode;
   //如果pred==null说明需要加入的位置为链表的第一个位置,则直接把新结点置为first,否则把index结点的前指针域结点的后指针域指                           //向新结点
//这样新节点就被插入了index结点的前面,成为了index结点
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

3. 从头部添加

看了上面的下面这个就很好理解了

/**
 * Links e as first element.
 */
private void linkFirst(E e) {
//首先拿到链表的第一个结点
    final Node<E> f = first;
    //新建结点,并把它的前指针域指向空,因为第一个结点前面没有结点了,并把它的后指针域指向第一个结点
    final Node<E> newNode = new Node<>(null, e, f);
   //把新结点置为first
    first = newNode;
   //当f == null时说明链表为空,那么新加进来一个后 last 和 first指向同一个结点,否则把f。前置指针域指向新节点,那么f就变成
//了第二个结点
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

4. 删除第一个结点

 

/**
 * Unlinks non-null first node f.
 */
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    //获取到一个结点的数据
    final E element = f.item;
    //获取到一个结点的后指针域指向(即第二个结点)
    final Node<E> next = f.next;
    //把它的数据指向置为null
    f.item = null;
    //断开它与下一个结点的连接
    f.next = null; // help GC
    //把第二个结点置为first
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

  添加的原理是建立指向,删除的原理就是断开指向,其他就不一一讲解了。

三,关于单向链表和双向链表

       单向链表只有一个指针域,只能指向下一个结点,如下是它的结点模型:

      如果想删除B只需要把A的指针指向C即可。

单向链表查询只能从前向后,而双向链表还可以从后往前,因为它有两个指针域,查询时可根据index值在size中的位置来确定是从前面查还是从后面查,这样可以提高查询效率。

 

 

    

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值