JDK源码解析之ArrayList和LinkedList

共同点:

两者都实现了List、Cloneable、Serializable接口,说明二者都可以序列化,但是需要注意使用subList方法用于获取部分list时由于返回的对象是SubList,SubList这个类没有实现序列化,如果此时进行序列化时就会抛出异常;具体的源码实现如下:

public List<E> subList(int fromIndex, int toIndex) {
    return new SubList<E>(this, fromIndex, toIndex);
}

不同点:

1.数据结构不同,ArrayList采用数组的形式存储,其有两个构造方法,开发者可以指定数组大小,默认构造函数大小为10

public ArrayList(int initialCapacity) {
super();
       if (initialCapacity < 0)
           throw new IllegalArgumentException("Illegal Capacity: "+
                                              initialCapacity);
this.elementData = new Object[initialCapacity];
   }
public ArrayList() {
this(10);
   }
个人建议初始化时最好能预判判断存储的大小,使用第一种方法初始化,避免空间浪费和扩容造成效率低下,如果容量不够,新增数据时会进行扩容,新建一个数组存放数据;扩容大小为原大小的一半加1,具体实现如下:

public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
   Object oldData[] = elementData;
   int newCapacity = (oldCapacity * 3)/2 + 1;
      if (newCapacity < minCapacity)
   newCapacity = minCapacity;
      // minCapacity is usually close to size, so this is a win:
      elementData = Arrays.copyOf(elementData, newCapacity);
}
  }


2. LinkedList采用链表存储数据,jdk1.6中采用双向循环链表存放,头结点为null,不存放数据,其节点为Entry,初始化size为0,构造方法如下:

private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;

/**
 * Constructs an empty list.
 */
public LinkedList() {
    header.next = header.previous = header;
}

 jdk1.7采用不带头结点的普通链表存储,具体的实现感兴趣可以看一下源码

3.两者的数据结构不同导致了不同操作的效率不同,接下来基于常用方法分析一下两者效率:

 a.对于基本的add(E e)操作,两者效率相同,前提是ArrayList不需要扩容情况下

 b.对于指定位置添加add(int index,E e)、romove(int index)操作,ArrayList需要进行数组的内容移动,而LinkedList只需要在指定位置添加一下新节点,变换一下指针即可,具体实现如下:

public void add(int index, E element) {
if (index > size || index < 0)
    throw new IndexOutOfBoundsException(
   "Index: "+index+", Size: "+size);

    ensureCapacity(size+1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
        size - index);
    elementData[index] = element;
    size++;
}

LinkedList源码

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

c.对于remove(Object o),二者都需要遍历进行查询,ArrayList查找到删除完节点后,还需要将其后的节点进行移动,LinkedList只需要改变一下指针指示即可,具体实现如下:

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 (int index = 0; index < size; index++)
          if (o.equals(elementData[index])) {
              fastRemove(index);
              return true;
          }  
   }
   return false;
}
 
  
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; // Let gc do its work
}

LikedList源码

public boolean remove(Object o) {
    if (o==null) {
        for (Entry<E> e = header.next; e != header; e = e.next) {
            if (e.element==null) {
                remove(e);
                return true;
            }
        }
    } else {
        for (Entry<E> e = header.next; e != header; e = e.next) {
            if (o.equals(e.element)) {
                remove(e);
                return true;
            }
        }
    }
    return false;
}
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;
}

总结:

1. 对于顺序添加读取,建议使用ArrayList且提前初始化好大小

2. 对于指定位置添加和删除,建议使用LinkedList,特别是位置比较靠前的新增和删除

3. 二者都是线程不安全的

4. subList方法的使用注意序列化问题,此处有坑

你可能感兴趣的:(JDK源码解析)