List
Vector:线程安全,本质和ArrayList一样是动态数组,方法都是通过sychronize实现安全的,不建议使用,可以用CopyOnWriteArrayList。
Stack:Vector子类,本质也是动态数组,它实现的是先进后出的栈,效率低下可以用ArrayDeque来替代。
ArrayList
1、object数组用于存储元素。所以底层是用数组实现。
2、size:记录长度
List<String> strList = new ArrayList<String>();
List<String> strList2 = new ArrayList<String>(2);
一个无参,一个有参。无参构造器没有指定数组长度,默认为10,有参可以自己指定长度。
public ArrayList() {
this(10);//给定默认值10,然后调用有参构造器
}
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity:" + initialCapacity);
this.elementData = new Object[initialCapacity];//创建一个长度为指定的数组,元素值默认为null
}
添加元素
public boolean add(E e) {
ensureCapacity(size + 1);//确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
elementData[size++] = e;//加入新元素e,size加1
return true;
}
每次数组做增加操作之前都要保证数组的大小够用,所以都会先调用ensureCapacity(size + 1)方法:
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;//获取数组大小(即数组的容量)
//当数组满了,又有新元素加入的时候,执行扩容逻辑
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3) / 2 + 1;//新容量为旧容量的1.5倍+1
if (newCapacity < minCapacity)//如果扩容后的新容量还是没有传入的所需的最小容量大或等于(主要发生在addAll(Collection<? extends E> c)中)
newCapacity = minCapacity;//新容量设为最小容量
elementData = Arrays.copyOf(elementData, newCapacity);//复制新容量
}
}
fail-fast机制
ArrayList不是线程安全的集合,当多线程环境下会出现ConcurrentModifyException,这个就命名为fail-fast机制。
代码实现在ArrayList的父类AbstractList中:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
// AbstractList中唯一的属性
// 用来记录List修改的次数:每修改一次(添加/删除等操作),将modCount+1
protected transient int modCount = 0;
// 返回List对应迭代器。实际上,是返回Itr对象。
public Iterator<E> iterator() {
return new Itr();
}
// Itr是Iterator(迭代器)的实现类
private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = -1;
// 修改数的记录值。
// 每次新建Itr()对象时,都会保存新建该对象时对应的modCount;
// 以后每次遍历List中的元素的时候,都会比较expectedModCount和modCount是否相等;
// 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
// 获取下一个元素之前,都会判断“新建Itr对象时保存的modCount”和“当前的modCount”是否相等;
// 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
...
}
说明一下这个流程:
1、有一个集合X,初始化的时候modCount的值为0.
2、有个线程A要遍历X,某一时刻创建了迭代器,此迭代器会记录此刻该线程的modCount为0.然后开始遍历,每遍历一个元素之前都会比较一下该迭代器记录的modCount和集合的modCount是否相同,如果不同证明其他线程修改了集合元素,则抛出异常。
3、假设上边迭代的过程中有个线程B同时去修改集合元素,每次修改都会更新集合的modCount的值。
解决这个问题的方法就是采用concurrent包下的CopyOnWriteList替代ArrayList。
LinkedList
核心属性
transient int size = 0;
transient Node<E> first;//链表第一个元素
transient Node<E> last;//链表最后一个元素
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}
添加元素
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++;
}
1、构造器为空实现,所以当执行完下面代码后,LinkedList也是个空的,first和last都是null
LinkedList<String> a = new LinkedList();
2、add()方法添加第一个元素
创建一个node1,该node pre为null,next为null,item为元素的值。
然后把first和last都设置为node1
3、调用add()方法添加第二个元素
创建一个node2,该node pre为node1,next为null,item为元素的值。
然后把last都设置为node2
和ArrayList比较,LinkedList是链表结构,数据存储不是连续的,每当删除元素的时候不需要重新排序老数据,所以更新效率高。
ArrayList和LinkedList按照索引访问数组如下区别
List a = new ArrayList();
a.add(2);
a.add(5);
a.add(1);
a.add(9);
System.out.println(a.get(3));//底层直接按照索引访问数组 this.elementData[var1]
List b = new LinkedList();
b.add(2);
b.add(5);
b.add(1);
b.add(9);
System.out.println(b.get(3));//底层 先比较链表长度的1/2 和3谁大 , 如果3大从后遍历链表,否则从前遍历链表
链表和数组的区别
- 数组:
- 优点
- 随机访问性强
- 查找速度快
- 缺点
- 插入和删除效率低
- 可能浪费内存
- 内存空间要求高,必须有足够的连续内存空间。
- 数组大小固定,不能动态拓展
- 优点
- 链表:
- 优点
- 插入删除速度快
- 内存利用率高,不会浪费内存
- 大小没有固定,拓展很灵活
- 缺点
- 不能随机查找,必须从第一个开始遍历,查找效率低
- 优点