集合总结笔记(jdk 1.6)——ArrayList/LinkedList

一、ArrayList

1.前言

ArrayList就是一个以数组形式实现的集合,集合元素允许为空、允许重复、有序、非线程安全(可以使用Collections.synchronizedList转成线程安全list)。其元素有private transient Object[] elementData 和private int size,size是自增或自减实现。

其中elementData transient 修饰,意味着不希望elementData数组被序列化。因为ArrayList里面的elementData未必是满的,没必要序列化整个elementData,ArrayList中重写了writeObject方法,遍历elementData,只序列化那些有的元素。

2.扩容

构造ArrayList的时候,默认的底层数组大小是10,ArrayList的底层是基于动态数组实现的原因,动态数组的意思就是指底层的数组大小并不是固定的,而是根据添加的元素大小进行一个判断,不够的话就动态扩容。扩容的时候把元素组大小先乘以3,再除以2,最后加1(这种默认扩容算法是因为如果一次性扩容扩得太大,必然造成内存空间的浪费;如果一次性扩容扩得不够,那么下一次扩容的操作必然比较快地会到来,这会降低程序运行效率,要知道扩容还是比价耗费性能的一个操作),最后调用到的是Arrays的copyOf方法,将元素组里面的内容复制到新的数组里面去。

3.添加元素

2.1、顺序添加:直接加到数组elementData后面

2.2、在指定index位置插入element:使用System.arraycopy(elementData, index, elementData, index +1,size - index)实现。把数组elementData index位置后面的元素复制到elementData,位置是从index +1,最后把elementData[index]赋值element。

4.删除元素

把指定元素后面位置的所有元素,利用System.arraycopy方法整体向前移动一个位置;最后一个位置的元素指定为null,这样让gc可以去回收它。(如果是按元素删除,ArrayList会先for循环找到元素对应的index下标)

5.ArrayList的优缺点

优点:ArrayList底层以数组实现,是一种随机访问模式,查找的时候非常快;ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已

缺点:删除元素和按照指定下标插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能

因此,ArrayList比较适合顺序添加、随机访问的场景。

二、LinkedList

1.前言

链表是一种线性的存储结构,意思是将要存储的数据存在一个存储单元里面,这个存储单元里面除了存放有待存储的数据以外,还存储有其下一个存储单元的地址(下一个存储单元的地址是必要的,有些存储结构还存放有其前一个存储单元的地址),每次查找数据的时候,通过某个存储单元中的下一个存储单元的地址寻找其后面的那个存储单元。

LinkedList是基于双向链表实现的,双向链表表示链表中任意一个存储单元都可以通过向前或者向后寻址的方式获取到其前一个存储单元和其后一个存储单元;链表的尾节点的后一个节点是链表的头结点,链表的头结点的前一个节点是链表的尾节点。

LinkedList的基本存储单元,它是LinkedList中的一个内部类:Entry<E>,里面有三个成员E element、Entry<E> next、Entry<E> previous。LinkedList元素允许为空、允许重复、有序、非线程安全。

存储结构如图:


2.添加元素

private transient Entry<E> header = new Entry<E>(null, null, null) //第一步
public LinkedList() {
    header.next = header.previous = header;
}
public boolean add(E e) {
     addBefore(e, header);
     return true;
}
private Entry<E> addBefore(E e, Entry<E> entry) {
    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);//第二步
    newEntry.previous.next = newEntry;//第三步
    newEntry.next.previous = newEntry;//第四步
    size++;
    modCount++;
    return newEntry;
}

第一步:实例化LinkedList对象时,会新建一个head的存储单元Entry,其element为空,next和previous指向head自己的引用地址;

第二步:新建要添加的存储单元newEntry,其element就是具体数据,next指向链表的head头节点,previous指向head的previous(即链表尾节点);

第三步:根据head找到newEntry的上个存储单元,把上个存储单元的next指向自己;

第四步:把head的previous指向自己;

3.查看元素

private Entry<E> entry(int index) {
    if (index < 0 || index >= size)
        throw new IndexOutOfBoundsException("Index: "+index+
                                            ", Size: "+size);
    Entry<E> e = header;
    if (index < (size >> 1)) {
        for (int i = 0; i <= index; i++)
            e = e.next;
    } else {
        for (int i = size; i > index; i--)
            e = e.previous;
    }
    return e;
}

由于LinkedList是双向链表,所以LinkedList既可以向前查找,也可以向后查找:当index小于数组大小的一半的时候(size >> 1表示size / 2,使用移位运算提升代码运行效率),向后查找;否则,向前查找。以index下标为点,循环到最后一个元素。

4.删除元素

private E remove(Entry<E> e) {
if (e == header)
    throw new NoSuchElementException();
 
        E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
       e.next = e.previous = null;
       e.element = null;
size--;
modCount++;
       return result;
}

通过下标index或者循环寻址判断element找到要删除Entry e,把e的上一个存储单元的next指向e的next,把e的下一个存储单元的previous指向e的previous,最后e的element、next、previous设为null。

三、ArrayList与LinkedList比较

(1)ArrayList以数组形式实现,顺序添加、查找快,插入、删除较慢;对于插入、删除元素慢在数组元素的批量copy,快在寻址

(2)LinkedList以链表形式实现,查找较慢,插入、删除方便;对于插入、删除元素慢在寻址,快在只需要改变前后Entry的引用地址

(3)ArrayList使用最普通的for循环遍历,LinkedList使用foreach循环比较快

 如果你十分确定你插入、删除的元素是在前半段,那么就使用LinkedList;如果你十分确定你删除、删除的元素在比较靠后的位置,那么可以考虑使用ArrayList;如果你不能确定你要做的插入、删除是在哪儿呢?建议LinkedList。一来LinkedList整体插入、删除的执行效率比较稳定,没有ArrayList这种越往后越快的情况;二来插入元素的时候,弄得不好ArrayList就要进行一次扩容,ArrayList底层数组扩容是一个既消耗时间又消耗空间的操作。

备注:

ArrayList参考:http://www.importnew.com/25008.html

LinkedList参考:http://www.importnew.com/25023.html

发表评论

邮箱地址不会被公开。 必填项已用*标注

昵称 *