一、ArrayList总结
简介:
ArrayList是集合框架中比较常用的数据结构,继承自 AbstractList,实现了List 、RandomAccess、Cloneable、Serializable 接口,所以底层基于数组实现容量大小动态变化,是支持快速访问、复制、序列化的。允许null存在。
成员变量:
ArrayList 底层是基于数组来实现容量大小动态变化的。
private int size;//容器中实际元素个数
transient Object[] elementData;
注意:上面的 size 是指 elementData 中实际元素个数,而 elementData.length 为集合容量,表示最多可以容纳多少个元素。
private static final int DEFAULT_CAPACITY = 10;
DEFAULT_CAPACITY是指ArrayList默认容量大小为10
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
这两个数组的意思是用来确定第一次添加元素时知道该 elementData 从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。
构造函数
无参构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
给elementData赋值了一个空数组。
有参构造函数
构造一个初始容量大小为 initialCapacity 的 ArrayList
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
使用指定 Collection 来构造 ArrayList 的构造函数
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
将 Collection 转化为数组并赋值给 elementData,把 elementData 中元素的个数赋值给 size。 如果 size 不为零,则判断 elementData 的 class 类型是否为 Object[],不是的话则做一次转换。 如果 size 为零,则把 EMPTY_ELEMENTDATA 赋值给 elementData,相当于new ArrayList(0)。
操作方法
add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
添加时先确定容量,然后size自增一,添加元素到相应位置,元素数量加1。
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++;
}
先检查下标是否越界,确定容量,修改modCount,然后使用System.arraycopy方法从将旧数组index位置的元素开始复制到新数组index+1的位置,将elementData的index位置放入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;
}
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; // clear to let GC do its work
}
当我们调用 remove(int index) 时,首先会检查 index 是否合法,然后再判断要删除的元素是否位于数组的最后一个位置。如果 index 不是最后一个,就再次调用 System.arraycopy() 方法拷贝数组。如果 index 是最后一个元素那么就直接将数组的最后一个位置制空, 当我们调用 remove(Object o) 时,会把 o 分为是否为空来分别处理。然后对数组做遍历,找到第一个与 o 对应的下标 index,然后调用 fastRemove 方法,删除下标为 index 的元素。
二、LinkedList总结
简介
LinkedList 是 Java 集合中比较常用的数据结构,与 ArrayList 一样,实现了 List 接口,只不过 ArrayList 是基于数组实现的,而 LinkedList 是基于链表实现的。所以 LinkedList 插入和删除方面要优于 ArrayList,而随机访问上则 ArrayList 性能更好。
除了 LIst 接口之外,LinkedList 还实现了 Deque,Cloneable,Serializable 三个接口。这说明该数据结构支持队列,克隆和序列化操作的。与 ArrayList 一样,允许 null 元素的存在,且是不支持多线程的。
成员变量
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
LinkedList有上面三个成员变量,size 为 LinkedList 的大小,first 和 last 分别为链表的头结点和尾节点。Node 为节点对象。
构造函数
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList() 仅仅构造一个空的列表。后一个构造方法构造一个包含指定 Collection 中所有元素的列表,该构造方法首先会调用空的构造方法,然后通过 addAll() 的方式把 Collection 中的所有元素添加进去。
操作方法
add操作
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
将新节点e置为first,如果原链表为空,last为newNode,如果不为空,f的前节点为newNode。大小加一,计数操作次数加一。
public void addLast(E e) {
linkLast(e);
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new 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));
}
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++;
}
首先检查下标是否越界,然后判断如果 index == size 则添加到末尾,否则将该元素插入的 index 的位置。其中 node(index) 是获取 index 位置的节点,linkBefore 负责把元素 e 插入到 succ 之前。
remove操作
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
先检查下标是否越界,然后调用 unlink 释放节点,
移除指定元素。
LinkedList和ArrayList区别和联系
1)数据结构的不同,ArrayList是基于数组实现的,LinkedList是基于双链表实现的。LinkedLIst还实现了Deque接口,Deque接口是Queue接口的子接口,
它代表一个双向队列,因此LinkedList可以作为双向队列 ,栈(可以参见Deque提供的接口方法)和List集合使用,功能强大。
2)Array可以直接返回数组种index位置的元素,因此在随机访问集合元素上有较好的性能,但是插入删除需要移动数组插入位置之后的所有元素。
3)相比于ArrayList,LinkedList随机访问集合元素时性能较差,因为需要在双向列表中找到要index的位置,再返回,但插入删除更快,LinkedList中插入或删除的时间复杂度仅为O(1)。
4)都实现了List接口
三、Vector总结
简介
Vector是矢量队列,它继承了AbstractList,实现了List、 RandomAccess, Cloneable, Serializable接口。因此可以实现队列的相应功能,可以随机访问、克隆、序列化。
成员变量
protected Object[] elementData;
protected int elementCount;
Vector内部通过一个Object数组来存储数据,使用elementCount变量来表示实际存储的元素个数。
构造函数
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
创建一个空的Vector,并且指定了Vector的初始容量和扩容时的增长系数。
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
创建一个空的Vector,并且指定了Vector的初始容量。
public Vector() {
this(10);
}
创建一个空的Vector,并且指定了Vector的初始容量为10。
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
根据其他集合来创建一个非空的Vector。
操作方法
add操作
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
首先看容量是否够,不够进行扩容操作,然后将元素e放置在数组的size位置。储存元素个数加一。
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
增加元素后,ArrayList中要存储的元素个数为minCapacity,若此时minCapacity > elementData原始的容量,则要扩容。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
首先获取elementData的原始容量,如果在构造方法中设置了capacityIncrement > 0,那么新数组长度就是原数组长度 + capacityIncrement。否则,新数组长度就是原数组长度 * 2。若进行扩容后,capacity仍然比实际需要的小,则新容量更改为实际需要的大小。如果新数组的长度比虚拟机能够提供给数组的最大存储空间大,则将新数组长度更改为最大正数:Integer.MAX_VALUE。然后按照新的容量newCapacity创建一个新数组,再将原数组中的内容copy到新数组中。
public void add(int index, E element) {
insertElementAt(element, index);
}
public synchronized void insertElementAt(E obj, int index) {
modCount++;
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
remove操作
public boolean remove(Object o) {
return removeElement(o);
}
调用removeElement方法来删除元素。
public synchronized boolean removeElement(Object obj) {
modCount++;
int i = indexOf(obj);
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
首先查找元素obj在数组中的下标,若下标 >= 0,调用removeElementAt(int)方法删除元素。
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
首先检查下标合法性,获取旧的元素值,计算需要移动的元素个数,移动元素删除。
三者的区别
1)底层实现:ArrayList:数组 ,Vector:数组, LinkedList:双向链表
采用数组作为数据存储结构的ArrayList、Vector查询速度快(可以根据下标直接取,比迭代查找更快),增删慢。LinkedList增删快,查询慢。
2)线程安全:Vector:线程安全,ArrayList:非线程安全,LinkedList:非线程安全,如果是开发中没有线程同步的需求,推荐优先使用ArrayList。因此其内部没有synchronized,执行效率会比Vector快很多。
3)扩容方面:由于ArrayList和Vector基于数组实现,当数组长度不够时,其内部会创建一个更大的数组,然后将原数组中的数据拷贝至新数组中。如果在创建Vector时不指定capacityIncrement(自动扩展长度)的值,如需扩展,则每次至少扩展至原长度的2倍。