LinkedList和ArrayList

145 阅读4分钟

ArrayList

类的定义

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

ArrayList 实现了 List 接口,继承了 AbstractList 抽象类,底层是基于数组实现的,并且实现了动态扩容。 RandomAccess说明支持随机读取,clonable说明支持克隆方法,Serializable则说明可以序列化。

transient Object[] elementData;//存储元素的数组

随机读取

public interface RandomAccess {
}

没有具体的实现,标记只是为了说明实现了这一功能。

arraylist中的具体实现:

public E get(int index) {
    rangeCheck(index);

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

克隆

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

序列化 明明声明成可序列化,但存储元素的数组element被transient所修饰了。
你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
所以一会支持序列化但要序列化的对象又不能序列化,这是要做什么?
先从Arraylist的机制说起,arraylist是支持动态扩容的,这就会导致实际的数组可能会一直填不满,一直都有没有用到的空间。这种情况下进行序列化,就会序列化那些没有用到的空间,这就会导致空间的浪费,序列化的时间也会增多。于是,ArrayList 做了一个愉快而又聪明的决定,内部提供了两个私有方法 writeObject 和 readObject 来完成序列化和反序列化。

arrayList的增删

添加方法: 向最后的位置添加

public boolean add(E e) {
//确保容量足够,不够则扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

向指定位置添加

public void add(int index, E element) {
//判断添加的位置是否正常
    rangeCheckForAdd(index);
    //确保容量
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //节点内容复制移动
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
                     //添加节点
    elementData[index] = element;
    size++;
}

确保容量的方法:

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

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

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

最重要的是超出容量大小调用grow方法

//参数为需要的容量最小大小。
private void grow(int minCapacity) {
    // overflow-conscious code
    //记录旧值
    int oldCapacity = elementData.length;
    //新值为旧值的1.5倍
    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);
}

还有一个初次调用时使用的grow,会返回一个element数组,和上面分析的差不多,就不写了。

remove:

public E remove(int index) {
//查询位置的检测,查询的坐标不能大于目前的长度
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);
    //位置计算,拷贝移除。
    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

    return oldValue;
}

Arraylist的特点

ArrayList 类实现了可变数组的大小,存储在内的数据称为元素。它还提供了快速基于索引访问元素的方式,对尾部成员的增加和删除支持较好。使用 ArrayList 创建的集合,允许对集合中的元素进行快速的随机访问,不过,向 ArrayList 中插入与删除元素的速度相对较慢。默认初始化容量为10,扩容时变为原来的1.5倍。

LinkedList

内部的定义 节点:

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

其他属性:

//大小
transient int size = 0;
//头尾节点
transient Node<E> first;
transient Node<E> last;

类定义

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

和arraylist一样的分析, 看看序列化部分:

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
    // Write out any hidden serialization magic
    s.defaultWriteObject();

    // Write out size
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (LinkedList.Node<E> x = first; x != null; x = x.next)
        s.writeObject(x.item);
}

LinkedList 在序列化的时候只保留了元素的内容 item,并没有保留元素的前后引用。这样就节省了不少内存空间
反序列化:

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
    // Read in any hidden serialization magic
    s.defaultReadObject();

    // Read in size
    int size = s.readInt();

    // Read in all elements in the proper order.
    for (int i = 0; i < size; i++)
        linkLast((E)s.readObject());
}

void linkLast(E e) {
    final LinkedList.Node<E> l = last;
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

注意 for 循环中的 linkLast() 方法,它可以把链表重新链接起来,这样就恢复了链表序列化之前的顺序。

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

linkedlist提供了查询收尾节点的方法。

添加节点: 普通add方法

public boolean add(E e) {
    linkLast(e);
    return true;
}
void linkLast(E e) {
    final LinkedList.Node<E> l = last;
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

指定位置的添加方法:

public void add(int index, E element) {
//位置检查
    checkPositionIndex(index);
 //如果是最后的位置
    if (index == size)
        linkLast(element);
    else
    //在指定位置添加
        linkBefore(element, node(index));
}

LinkedList 类采用链表结构保存对象,这种结构的优点是便于向集合中插入或者删除元素。需要频繁向集合中插入和删除元素时,使用 LinkedList 类比 ArrayList 类效果高,但是 LinkedList 类随机访问元素的速度则相对较慢。这里的随机访问是指检索集合中特定索引位置的元素。