title: ArrayList与LinkedList categories:

  • 数据结构

##1. 关系图

还是只放一个UML图吧,蓝色的箭头是继承关系,绿色虚线箭头是实现的接口

ArrayList

LinkedList

##2. 数组与链表的区别

在内存上,数组是连续地址,而链表是离散地址;数组通过下标定位,而链表只能通过指针定位;

||get(index)|contain(obj)|add/remove(index,obj)|空间|| |: |ArrayList |O(1)| O(N)|O(N) | O(N)| |LinkedList | O(N) | O(N) |O(1)* |O(N)

  • 这里的get可以用“定位/index”翻译,而contain可以翻译为“查找/Search”,两者翻译不可混为一谈;
  • add/remove这里有争议,特指已经查找到了元素位置的情况下,移动数据所需要的时间;
  • 不考虑第一与最后位的特殊情况

####1. 定位(get(index))

数组通过下标进行定位

 E elementData(int index) {
        return (E) elementData[index];
    }

链表需要遍历整个节点

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;
        }
    }

####2. 搜索(contain(obj))

在数组中,contain实际上就是indexof,因为需要遍历查表对比,所以复杂度都是O(N)

public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -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;
    }

####3. 添加或删除(add/remove(index,obj))

在数组中,插入元素需要调整后面的所有元素,所以消耗比较多,我们以add为例,可以看出,是先复制后添加的,复杂度为O(N);

public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //使用native代码以提高效率
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

而在链表中,add时间为{定位时间 + O(1)},我们平时说的添加删除的复杂度O(1)是指已经获得当前定位的情况,在stackoverFlow上也有关于这个的讨论,你要说到底我支不支持O(1)呢,那我当然支持了,毕竟都是那么写的嘛

public void add(int index, E element) {
        checkPositionIndex(index);
		
        if (index == size)
            linkLast(element);
        else
            //这里node()定位的复杂度已经是O(N)了,但是我们不考虑
            linkBefore(element, node(index));
    }
    
/**
 * Inserts element e before non-null Node succ.
 * 这里的元素数据复杂度为O(1)
 */
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}
    

4. 总结

总得来说,就是Array定位比LinkedList快,而插入删除(不包括定位时间的情况)操作LinkedList更快。另外再说一下,比如Android的ListView场景中很少存在插入数据的情况,大多数情况就是在末尾添加数据,所以没必要为了这点性能而放弃ArrayList。