java List学习
list有序列表(有顺序,可重复)
Java 的 List 是非常常用的数据类型。 List 是有序的 Collection
。 Java List 一共三个实现类:
分别是 ArrayList
、 Vector
和 LinkedList
。
list接口结构图
ArrayList(数组,最常用的 List 实现类)
- 基于数组实现,无容量的限制。
- 在执行插入元素时可能要扩容,在删除元素时并
不会减小数组的容量
,在查找元素时要遍历数组,对于非null的元素采取equals的方式寻找。 - 是
非线程安全
的。 - 注意点:
- (1)ArrayList随机元素时间复杂度O(1),插入删除操作需大量移动元素,效率较低
- (2)为了节约内存,当新建容器为空时,会共享
Object[] EMPTY_ELEMENTDATA = {}
和Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}空数组
- (3)容器底层采用数组存储,每次扩容为
1.5倍
- (4)ArrayList的实现中大量地调用了Arrays.copyof()和System.arraycopy()方法,其实Arrays.copyof()内部也是调用System.arraycopy()。System.arraycopy()为Native方法
- (5)两个ToArray方法
- Object[] toArray()方法。该方法有可能会抛出java.lang.ClassCastException异常
- T[] toArray(T[] a)方法。该方法可以直接
将ArrayList转换得到的Array进行整体向下转型
- (6)ArrayList
可以存储null值
- (7)ArrayList每次修改(增加、删除)容器时,都是修改自身的
modCount
;在生成迭代器时,迭代器会保存该modCount值,迭代器每次获取元素时,会比较自身的modCount与ArrayList的modCount是否相等,来判断容器是否已经被修改,如果被修改了则抛出异常(fast-fail机制)。
总结
ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问
。数
组的缺点是每个元素之间不能有间隔
, 当数组大小不满足时需要增加存储能力,就要将已经有数
组的数据复制到新的存储空间中。 当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进
行复制、移动、代价比较高。因此,它适合随机查找和遍历
,不适合插入和删除。
源码解读
成员变量
/**
* Default initial capacity.默认数组容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.长度为0的数组
*
*/
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.默认长度为10的数组
*
*/
//两个空的数组有什么区别呢?简单来讲就是第一次添加元素时知道该 elementData 从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
* 注意: size 是指 elementData 中实际有多少个元素,而 elementData.length 为集合容量,表示最多
* 可以容纳多少个元素。
*
* @serial
*/
private int size;// 实际元素个数
构造方法
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
//注意:注释是说构造一个容量大小为 10 的空的 list 集合,但构造函数了只是给 elementData 赋值了一个空的数组,其实是在第一次添加元素时容量扩大至 10 的
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
添加
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++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
由以上源码可知,add(int index, E element),addAll(Collection<? extends E> c),addAll(int index, Collection<? extends E> c) 操作是都是先对集合容量检查 ,以确保不会数组越界。然后通过 System.arraycopy() 方法将旧数组元素拷贝至一个新的数组中去。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//即使初始化时指定大小 小于10个,添加元素时会调整大小,保证capacity不会少于10个。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);//Explicit:明确的
}
private void ensureExplicitCapacity(int minCapacity) {
//这个变量是定义在 AbstractList 中的。记录对 List 操作的次数。主要使用是在 Iterator,是防止在迭代的过程中集合被修改
modCount++;
// overflow-conscious code,
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
由此可见:每次添加元素到集合中时都会先确认下集合容量大小。然后将 size 自增 1。ensureCapacityInternal 函数中判断如果 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA
就取 DEFAULT_CAPACITY 和 minCapacity 的最大值也就是 10。这就是 EMPTY_ELEMENTDATA 与 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的区别所在。同时也验证了上面的说法:使用无惨构造函数时是在第一次添加元素时初始化容量为 10 的。ensureExplicitCapacity 中对 modCount 自增 1,记录操作次数,然后如果 minCapacity 大于 elementData 的长度,则对集合进行扩容。显然第一次添加元素时 elementData 的长度为零。那我们来看看 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);
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
//Arrays.copyOf底层是System.arrayCopy
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
很简单明了的一个函数,默认将扩容至原来容量的 1.5 倍。但是扩容之后也不一定适用,有可能太小,有可能太大。所以才会有下面两个 if 判断。如果1.5倍太小的话,则将我们所需的容量大小赋值给newCapacity,如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE
来扩容。然后将原数组中的数据复制到大小为 newCapacity 的新数组中,并将新数组赋值给 elementData。用图片理解的过程就是如下图所示(图片来自这篇文章)
删除
//删除 remove(o)
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(index)
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;
}
当我们调用 remove(int index) 时,首先会检查 index 是否合法,然后再判断要删除的元素是否位于数组的最后一个位置。如果 index 不是最后一个,就再次调用 System.arraycopy() 方法拷贝数组。
说白了就是将从 index + 1 开始向后所有的元素都向前挪一个位置。然后将数组的最后一个位置空,size - 1。
如果 index 是最后一个元素那么就直接将数组的最后一个位置空,size - 1即可。
当我们调用 remove(Object o) 时,会把 o 分为是否为空来分别处理。然后对数组做遍历,找到第一个与 o 对应的下标 index,然后调用 fastRemove 方法,删除下标为 index 的元素。
其实仔细观察 fastRemove(int index) 方法和 remove(int index) 方法基本全部相同。用图片理解的过程就是如下图所示(图片来自这篇文章)
获取
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];
}
更新
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
包含
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
遍历
有使用过集合的都知道,在用 for 遍历集合的时候是不可以对集合进行 remove操作的,因为 remove 操作会改变集合的大小。从而容易造成结果不准确甚至数组下标越界,更严重者还会抛出 ConcurrentModificationException。foreach 遍历等同于 iterator。为了搞清楚异常原因,我们还必须过一遍源码。
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic(heap:堆)
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
从源码可以看出,ArrayList 定义了一个内部类 Itr 实现了 Iterator 接口。在 Itr 内部有三个成员变量。
cursor:代表下一个要访问的元素下标。
lastRet:代表上一个要访问的元素下标。
expectedModCount:代表对 ArrayList 修改次数的期望值,初始值为 modCount。
下面看看 Itr 的三个主要函数。
hasNext
实现比较简单,如果下一个元素的下标等于集合的大小 ,就证明到最后了。
next
方法也不复杂,但很关键。首先判断 expectedModCount 和 modCount 是否相等。然后对 cursor 进行判断,看是否超过集合大小和数组长度。然后将 cursor 赋值给 lastRet ,并返回下标为 lastRet 的元素。最后将 cursor 自增 1。开始时,cursor = 0,lastRet = -1;每调用一次 next 方法, cursor 和 lastRet 都会自增 1。
remove
方法首先会判断 lastRet 的值是否小于 0,然后在检查 expectedModCount 和 modCount 是否相等。接下来是关键,直接调用 ArrayList 的 remove 方法删除下标为 lastRet 的元素
。然后将 lastRet 赋值给 cursor ,将 lastRet 重新赋值为 -1,并将 modCount 重新赋值给 expectedModCount。
下面我们一步一步来分析 Itr 的操作。如图一所示,开始时 cursor 指向下标为 0 的元素,lastRet 指向下标为 -1 的元素,也就是 null。每调用一次 next,cursor 和lastRet 就分别会自增 1。当 next 返回 “C” 时,cursor 和 lastRet 分别为 3 和 2 [图二]。
此时调用 remove,注意是 ArrayList 的 remove
,而不是 Itr 的 remove。会将 D E 两个元素直接往前移动一位,最后一位置空,并且 modCount 会自增 1。从 remove 方法可以看出。[图三]。
此时 cursor = 3,size = 4,没有到数组末尾,所以循环继续。来到 next 方法,因为上一步的 remove 方法对 modCount 做了修改 ,致使 expectedModCount 与 modCount 不相等,这就是 ConcurrentModificationException 异常的原因所在。从例子中也可以看出异常出自 ArrayList 中的内部类 Itr 中的 checkForComodification 方法。
总结: ArrayList 底层基于数组实现容量大小动态可变。 扩容机制为首先扩容为原始容量的 1.5 倍。
如果1.5倍太小的话,则将我们所需的容量大小赋值给 newCapacity,如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE
来扩容。
扩容之后是通过数组的拷贝来确保元素的准确性的,所以尽可能减少扩容操作。
ArrayList 的最大存储能力:Integer.MAX_VALUE。
size 为集合中存储的元素的个数。
elementData.length 为数组长度,表示最多可以存储多少个元素。
如果需要边遍历边 remove ,必须使用 iterator。且 remove 之前必须先 next,next 之后只能用一次 remove。
LinkedList(双向链表)
- 基于双向链表机制
- 在插入元素时,须创建一个新的Entry对象,并切换相应元素的前后元素的引用;在查找元素时,须遍历链表;在删除元素时,须遍历链表,找到要删除的元素,然后从链表上将此元素删除即可。
- 是
非线程安全
的。 - 注意:
- (1)LinkedList
有两个构造函数
,一个为无參构造,只是新建一个空对象,第二个为有参构造,新建一个空对象,然后把所有元素添加进去。 - (2)LinkedList的存储单元为一个名为Node的内部类,包含pre指针,next指针,和item元素,实现为双向链表
- (3)LinkedList的删除、添加操作时间复杂度为O(1),查找时间复杂度为O(n),查找函数有一定优化,容器会先判断查找的元素是离头部较近,还是尾部较近,来决定从头部开始遍历还是尾部开始遍历
- (4)LinkedList实现了Deque接口,因此也可以作为栈、队列和双端队列来使用。
- (5)LinkedList可以存储null值
LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除
,随机访问和遍历速度比较
慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆
栈、队列和双向队列使用
成员变量
- transient int size = 0;
- transient Node first;
- transient Node last;
构造方法
public LinkedList() {
}
添加 add(e)
这里用网上的图更容易形象的理解,图片来自这篇文章
//add 方法直接调用了 linkLast 方法,而 linkLast 方法是不对外开放的。该方法做了三件事情,新增一个节点,改变其前后引用,将 size 和 modCount 自增 1。其中 modCount 是记录对集合操作的次数。
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)//Node为第一个元素
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
//添加 add(index,e) 在指定的位置插入元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
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 之前。
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//可以看出 node() 方法这里写的还是挺赞的,不是傻乎乎的从头到尾或者从尾到头遍历链表,而是将 index 与 当前链表的一半做对比,比一半小从头遍历,比一半大从后遍历。对于数据量很大时能省下不少时间。
删除 remove(o)
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;
}
//先检查下标是否越界,然后调用 unlink 释放节点。
//删除 remove(index)
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//判断要移除的元素是否为 null,然后在遍历链表,找到钙元素第一次出现的位置,移除并返回 true。
获取
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
更新
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
包含
public boolean contains(Object o) {
return indexOf(o) != -1;
}
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
遍历
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
由源码可以看出初始化 ListItr 时,将 nextIndex 指向 index, 也就是零。如果该集合为空,那么 index == size 为 true,next 指向 null,否则 next 指向下标为零的元素,也就是第一个。 hasNext 直接返回 nextIndex < size 简单明了。下面看看 next 方法,首先检查 expectedModCount 与 modCount 是否相等,看似无关紧要的代码保证了集合在迭代过程中不被修改[包括新增删除节点等]。然后将 lastReturned 指向 next,next 后移一个节点,nextIndex 自增 1,并返回 lastReturned 节点的元素。
总结
- 从源码可以看出 LinkedList 是基于链表实现的。如下图:
- 在查找和删除某元素时,区分该元素为 null和不为 null 两种情况来处理,LinkedList 中允许元素为 null。
- 基于链表实现不存在扩容问题。
- 查找时先判断该节点位于前半部分还是后半部分,加快了速度
- 因为基于链表,所以插入删除极快,查找比较慢。
- 实现了栈和队列的相关方法,所以可作为栈,队列,双端队列来用。
Vector( 数组实现、 线程同步)
-
基于synchronized实现的线程安全的ArrayList
,但在插入元素时容量扩充的机制和ArrayList稍有不同,并可通过传入capacityIncrement来控制容量的扩充。
Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步
,即某一时刻只有一
个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费
,因此,
访问它比访问 ArrayList 慢 。
成员变量
- protected Object[] elementData;
- protected int elementCount;
- protected int capacityIncrement;
构造方法
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector() {
this(10);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
添加
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
删除
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;
}
扩容
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);
}
获取
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
更新
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
包含
public boolean contains(Object o) {
return indexOf(o, 0) >= 0;
}
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;
}
Stack
- 基于Vector实现,支持LIFO(后进先出)。
类声明
public class Stack<E> extends Vector<E> {}
push
public E push(E item) {
addElement(item);
return item;
}
pop
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
peek
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
CopyOnWriteArrayList(推荐看一下这篇blog)
- 是一个线程安全、并且在
读操作时无锁
的ArrayList。 - 很多时候,我们的系统应对的都是读多写少的并发场景。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。
- 优点
- 1)采用
读写分离方式
,读的效率非常高 - 2)CopyOnWriteArrayList的迭代器是基于创建时的数据快照的,故数组的增删改不会影响到迭代器
- 缺点
- 1)内存占用高,每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC(空间换取时间)
- 2)只能保证数据的最终一致性,不能保证数据的实时一致性。写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。
成员变量
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
构造方法
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
final void setArray(Object[] a) {
array = a;
}
添加(有锁,锁内重新创建数组)
final Object[] getArray() {
return array;
}
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
存在则添加(有锁,锁内重新创建数组)
- 先保存一份数组snapshot,如果snapshot中存在,则直接返回。
- 如果不存在,那么加锁,获取当前数组current,比较snapshot与current,遍历它们共同长度内的元素,如果发现current中某一个元素等于e,那么直接返回(当然current与snapshot相同就不必看了);
- 之后再遍历current单独的部分,如果发现current中某一个元素等于e,那么直接返回;
- 此时可以去创建一个长度+1的新数组,将e加入。
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
//如果snapshot与current元素不同但current与e相同,那么直接返回(扫描0到common)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
- // 如果current中存在e,那么直接返回(扫描commen到len)
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
删除(有锁,锁内重新创建数组)
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
获取(无锁)
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
更新(有锁,锁内重新创建数组)
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
- // 为了保持“volatile”的语义,任何一个读操作都应该是一个写操作的结果,
- 也就是读操作看到的数据一定是某个写操作的结果(尽管写操作没有改变数据本身)。
- 所以这里即使不设置也没有问题,仅仅是为了一个语义上的补充(就如源码中的注释所言)
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
包含(无锁)
public boolean contains(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length) >= 0;
}
遍历(遍历的是获取iterator时的数组快照)
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}
static final class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public boolean hasPrevious() {
return cursor > 0;
}
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
@SuppressWarnings("unchecked")
public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor-1;
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code remove}
* is not supported by this iterator.
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code set}
* is not supported by this iterator.
*/
public void set(E e) {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code add}
* is not supported by this iterator.
*/
public void add(E e) {
throw new UnsupportedOperationException();
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
Object[] elements = snapshot;
final int size = elements.length;
for (int i = cursor; i < size; i++) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
action.accept(e);
}
cursor = size;
}
}
List实现类之间的区别
- (1) 对于需要快速插入,删除元素,应该使用LinkedList。
- (2) 对于需要快速随机访问元素,应该使用ArrayList。
- (3) 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList)。
- (4) 对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector、CopyOnWriteArrayList)。
这篇blog是我查看网上资料综合起来的,在此用于学习记录下来