您的位置:首页 > 编程语言 > Java开发

JDK源码解析之ArrayList和LinkedList

2017-09-23 16:16 555 查看
共同点:

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

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<E>,初始化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方法的使用注意序列化问题,此处有坑
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息