包括:
一. Colleciton 与List
二. ArrayList
三. Vector
四. LinkedList
成员变量如下:
private static final int DEFAULT_CAPACITY = 10; //默认初始值
transient Object[] elementData; //存放数据的数组
private int size; //ArrayList包含的元素个数(不是数组大小)
private static final Object[] EMPTY_ELEMENTDATA = {}; //构造函数所使用,可参考以下的构造函数
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //默认构造函数所使用,可参考以下的构造函数
上述用一个 transient 修饰,这样当我们持久化对象实例的时候,transient修饰的值不会在序列化列表中。
Ps:static 修饰的值也不会在序列化列表中。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
像平常使用的如下代码就会使用预先创建好的空数组。 那么,当我们 add() 的时候,是如何创建数组,如何使用默认值的呢?
List list = new ArrayList();
当我们使用 add(E e) 方法的时候,先执行如下函数:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensurecapacityInternal(size + 1) 就会确保目前的数组不会是空数组。实现如下:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
首次调用 add(E e) 的时候,size 为 0,所以 size + 1 = 1 < DEFAULT_CAPACITY = 10,此时取最小容量为10。最后会调用 grow 方法进行对数组的创建:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
这就是一个首次创建的过程
可以看到,每次进行 add(E e)方法的时候,都会检测数组容量,当快要溢出的时候,就会进行扩容操作。所以如果我们明确所插入的元素的多少,最好指定一个初始容量值,避免过多的扩容而浪费时间。
Ps:平常我们都说 ArrayList 不适合增删频繁的操作,这句话不是很严谨,根据 ArrayList 的add(E e) 函数的 elementData[size ++] = e可以知道,如果不进行扩容操作的话,增加方法还是有着很高的效率的。
构造函数(二). 如下:
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);
}
}
可以看到,我们可以指定初始容量。其实为了避免频繁的扩容,最好指定初始容量。
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;
}
该remove() 方法 使用 for 循环查找,再 equals 方法比较,找到接着删除,删除方法如下:
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
}
可以看到,删除的方法会比增添的方法麻烦一点,因为会把删除位置的元素的后面元素全部往前移动一位。除此之外,特定位置的插入和删除操作也都会引起数组数据的移动,这样是非常耗时低效的。
总结:
- 基本信息:可以加入空值,重复值,非同步,基于数组实现。
- 适用:特定位置的增删操作不多,以及删除操作不多。
- 注意事项:适用的时候最好初始化容量。
成员变量:
protected Object[] elementData; //存放数据
protected int elementCount; //数组中元素的个数
protected int capacityIncrement; //如果该值小于等于0.那么Vector容量双倍增长
构造函数:
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
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 的容量为10,capacityIncrement 为0。
普通方法:
1.add(E e)方法
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
modCount 暂时不管, 对于 ensureCapacityHelper ,如下代码:
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
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);
}
从代码可以看出,当容量满了之后,新的容量大小 = 原始容量大小 + capacityIncrement / 原始容量 大小。当 capacityIncrement >=0 的时候,那么新的容量就是原始容量的两倍,否则,新的容量 = 原始容量 + capacityIncrement 的值。所以,最好我们还是要指定capacityIncrement,否则每次扩容都会双倍增长。另外,从上面的 add(E e)方法中的 elementData[elementCount++] = e;可以知道,Vector 的插入数据的方式还是挺高效的。
2.remove()方法:
public boolean remove(Object o) {
return removeElement(o);
}
public synchronized boolean removeElement(Object obj) {
modCount++;
int i = indexOf(obj);
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
public synchronized int indexOf(Object o, int index) {
if (o == null) {
for (int i = index ; i < elementCount ; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = index ; i < elementCount ; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}
那么对于remove()方法,也会普通的for循环找出位置,再删除,删除的时候后续元素往前移动一位。当然,这也是比较低效率的。和ArrayList 一样,特定位置的插入和删除也都会引起数组的数据移动,这是非常耗时低效的。
总结:
- 基本信息:可以加入空值,重复值,同步,基于数组实现。
- 适用:特定位置的增删操作不多,以及删除操作不多。
- 注意事项:适用的时候最好初始化容量。
成员变量:
transient int size = 0; //数量
transient Node<E> first; //首节点
transient Node<E> last; //最后节点
对于其Node 节点:
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;
}
}
构造函数:
public LinkedList() {
}
public boolean add(E e) {
linkLast(e);
return true;
}
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++;
}
可以看到,首先创建一个 newNode 节点,再创建的时候,完成部分的节点连接,如下代码:
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
最后,在LinkLst(E e) 中 完成节点的连接。
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
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;
}
对于remove()方法,先for 循环遍历,然后再像普通链表操作一样移除节点。
那么对于链表的实现方式来说,增删操作会强于ArrayList,Vector等,因为删除操作,特定位置的增删操作,ArrayList,Vector还需要把该位置往后的位置都向前移动或者需要扩容,浪费了时间,基于链表的实现方法没有这种问题。
- 基本信息:可以加入空值,重复值,不同步,基于链表的实现。
- 适用:增删操作多的情况中。