List接口的特点
list底层结构是一个序列、存储内容时直接在内存中开辟一个空间、然后将空间地址与索引对应。
实现list接口的用户、可以直接堆列表中元素的插入位置进行确定、根据元素的索引来访问元素
允许重复元素
实现List接口的集合特点
ArrayList
底层数据结构-数组、支持随机访问、在内存中分配连续的空实现了数组长度可变性
遍历和查找元素效率高
增加和删除效率低、O(n)
初始容量10
非线程安全
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
private static final long serialVersionUID = 8683452581122892189L;
transient Object[] elementsDate;
int size ; //自动改变size机制
public ArrayList(int initialCapacity)//构造一个具有指定初始容量的空列表。
public ArrayList() //默认构造一个初始容量为10的空列表。
public ArrayList(Collection<? extends E> c)//构造一个包含指定 collection 的元素的列表
int newCapacity=oldCapacity+(oldCapacity>>1); // 1.5倍的方式扩容
Serializable是序列化的,目的是将对象状态信息持久的保存起来
在java中,对象存储在JVM堆上面、JVM运行时对象存在当JVM的停止运行、对象状态可能会丢失。想要永久的存储对象信息、因此需要读取对象信息出去、实现序列化。
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
transient 修饰的属性,不会被序列化
静态的属性能不能被序列化和反序列化
实现Serializable接口时要给serialVersionUID赋值,因为API文档里面规定描述:
如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会 InvalidClassException。
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,在反序列化时
由于计算默认的 serialVersionUID 对类的详细信息有较高的敏感性、编译器实现的不同可能千差万别,可能会抛出InvalidClassException。
ArrayList的add和remove、clear都有改变modcount,++操作
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
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;
}
快速失败机制
fast-fail机制是java集合中的一种错误检测机制、会尽力的在不同步修改操作中尽最大努力去抛出异常,所以这种机制一般仅用于检测bug。
示例
1、多线程
当多线程操作一个集合、线程A 使用Iterator 遍历时、线程B改变了集合内容、线程A抛出异常。
原因:集合类通过modcount机制来记录集合被改变的次数、Iterator 遍历会判断modcount 和初始值是都一样、不一样抛出ConcurrentModificationException。
2、单线程 使用hashmap、arraylist都可能出现fast-fail错误机制
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0 ; i < 6 ; i++ ) {
list.add(i + " ");
}
Iterator<String> iterator = list.iterator();
int i = 0 ;
while(iterator.hasNext()) {
if (i == 3) {
list.remove(3);
}
System.out.println(iterator.next());
i ++;
}
}
在iterator时、list 删除 remove一个元素,会发生fail-fast。
ArrayList中快速失败机制
在arraylist.iterator()
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 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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
modCount != expectedModCount抛出该异常。
expectedModCount初始值默认等于modCount,当ArrayList进行add()、remove()、clear()等更改集合元素方法的时候、modCount ++。
当元素集合放生改变时、modCount 发生变化、使得modCount != expectedModCount、抛出异常。
ArrayList:
1、 在使用iterator遍历时、不能使用list.add() 、list.remove() 操作、只能用 iterator.remove();
2、 Iterator的设计实在迭代时不允许其他线程修改、增加了 checkForComodification();
iterator.remove()可以使用是因为iterator.remove()时自己修改自己不存在并发修改、并且迭代器remove会重置expectedModCount、将cursor向前移动一位。
解决:
1、modCount 加关键字Synchionzed
2、使用java并发包(java.util.concurrent)中的类来代替 ArrayList 和hashMap
HashMap,可以使用ConcurrentHashMap, ConcurrentHashMap采用了锁机制,是线程安全的。
在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。即迭代不会发生fail-fast,但不保证获取的是最新的数据。
CopyOnWriterArrayList中非快速失败机制
安全失败是指、遍历集合时、在拷贝的集合上遍历操作、集合元素发生变化时、是不影响遍历过程的。
1、 在使用iterator遍历时、不能iterator.remove()使用操作、只能用 list.add() 、list.remove() ;
原因:当然是因为CopyOnWriterArrayList适用于多线程的,他的迭代器的实现类COWIterator会在创建时复制一份list副本、之后迭代的是父本、所以链表改变元素无影响。但 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。
Iterator本身存在于副本、删除副本元素不存在意义、如果去删除原始list、在并发的情况下可能和创建迭代器的副本已经完全不同了
Vector
线程安全的集合、同步的
vector初始容量为10、2倍方式扩容、可指定扩容大小
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public Vector()//使用指定的初始容量和等于0的容量增量构造一个空向量。
public Vector(int initialCapacity)//构造一个空向量,使其内部数据数组的大小,其标准容量增量为零。
public Vector(Collection<? extends E> c)//构造一个包含指定 collection 中的元素的向量
public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量构造一个空的向量
LinkedList
底层数据结构-双向链表、采用链表的存储方式、不支持随机访问
遍历和查找元素效率低
增加和删除效率高
非线程安全
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
Deque方法 addLast addFirst
offerLast offerFirst
removeLast removeFirst
pollLast pollFirst