区别:
ArrayList底层是数组结构,查询、修改快O(1),增加删除慢O(n),线程不安全,效率较高。
LinkedList底层是双向链表数据结构,查询、修改慢O(n),增删快O(1),线程不安全,效率高。
Vector 底层是数组结构,可以看作是 ArrayList 的线程安全版本,效率较低。
注意:
所谓的线程安全,不包括对这些集合类的复合操作;当第一个线程对复合操作时,第二个线程也进入未同步的复合操作,就会出现问题。但多线程单个去操作线程安全集合中的方法时,是可靠安全的
该问题可参考:https://blog.youkuaiyun.com/z960339491/article/details/73293894
ArrayList:
注意点:
初始大小:10
扩容计算:int newCapacity = oldCapacity + (oldCapacity >> 1);向右移位1相当于除二,即扩容为原来的1.5倍
建议设置 ArrayList 初始容量,因为这样能够节省掉很多次内存分配空间和数据搬移的操作。
不能存储八大基本数据类型,需要使用其对应的包装类。
为什么ArrayList是线程不安全的:
可以认为ArrayList是自增扩容的数组,在对其进行add操作时,源码中会调用到private void grow(int minCapacity)方法,这个方法是非同步的,所以多线程操作时,一旦线程在这个方法中被挂起,其他线程继续操作就会出现问题
三种不同的List创建方法
public static void main(String[] args) {
// List<String> data = new ArrayList<>(); //这种方法创建的data,在多个线程对他同时的操作中,会出现问题
// List<String> data=new Vector<>(); //线程安全
List<String> data = Collections.synchronizedList(new ArrayList<>()); //这种方法创建的data,是线程安全的
// CopyOnWriteArrayList<String> data = new CopyOnWriteArrayList(); //该方法创建的data,操作速度为什么会比上面的慢很多??
// 用来让主线程等待100个子线程执行完毕
CountDownLatch countDownLatch = new CountDownLatch(10000);
// 启动100个子线程
for (int i = 0; i < 10000; i++) {
SampleTask task = new SampleTask(data, countDownLatch);
Thread thread = new Thread(task);
thread.start();
}
try {
// 主线程等待所有子线程执行完成,再向下执行
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// List的size,正常情况下,10000*100
System.out.println(data.size());
}
}
class SampleTask implements Runnable {
CountDownLatch countDownLatch;
List<String> data;
public SampleTask(List<String> data, CountDownLatch countDownLatch) {
this.data = data;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// 每个线程向List中添加100个元素
for (int i = 0; i < 100; i++) {
data.add("添加第 " + i + "个元素!");
}
// 完成一个子线程
countDownLatch.countDown();
}
remove(int index):
将指定下标后面一位到数组末尾的全部元素向前移动一个单位,并且把数组最后一个元素设置为null,这样方便之后将整个数组不再使用时,会被GC
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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;
}
为什么不能在 ArrayList 的 For-Each 循环中删除元素
现象:
List<Object> integers = new ArrayList<>();
integers.add("0");
integers.add("1");
for (Object o : integers) {
if ("1".equals(o)) {
integers.remove(o);
}
}
System.out.println(integers);
在使用 for-each 方式删除 ArrayList 中非倒数第二个元素时,会抛出异常(ConcurrentModificationException)。但在使用其他方式删除 ArrayList 元素时,却没有问题。
原因:
for-each 方式在遍历 ArrayList 中的元素时,会使用内部类 Itr (Itr 实现了 Iterator 接口)的 hasNext() 和 next() 方法。
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
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
1. 在调用 ArrayList.remove( object ) 时,最终会执行下面的 fastRemove 方法。ArrayList.modCount 会自增1,并且会对 ArrayList 的 size 自减操作;但 ArrayList 内部类 Itr.expectedModCount 值不会改变;
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
}
2. foreach循环中,被调用到 Itr 的 hasnext() 方法时,如果上一步移除的是倒数第二个元素, 那么 cursor == size(cursor 的值来自于上一次调用 Itr.next() 方法确定,为原size()-2+1 = 原size()-1;此时 size 也为原size()-1),hasnext 将返回false,foreach循环停止;其他情况的hasnext将true,foreach循环继续执行;
3. 此时会调用 Itr 的 next 方法,然后执行 checkForComodification 方法,该方法检查 ArrayList 的 modCount 和 内部类 Itr.expectedModCount 的值是否相等,由于第一步中的操作,两者不相等,所以抛出异常。
为什么Vector是线程安全的:
该类中的方法都是加入了synchronized关键字的。
ArrayList、LinkedList、Vector源码解析参考:https://www.cnblogs.com/lxd-ld/p/9927214.html
CopyOnWriteArrayList
特点:
1. 使用 ReentrantLock 保证线程安全
2. 增、删、改操作都会复制一份新数组,在新数组中做修改,修改完了再用新数组替换老数组
3. 采用读写分离的思想,读操作不加锁,写操作加锁,且写操作空间复杂度为O(2n)(n为数组长度),所以适用于读多写少的场合
4.由于读操作不加锁,所以 CopyOnWriteArrayList 只保证最终一致性,不保证实时一致性
Queue:LinkedList适合FIFO的队列
/**
* 队列测试:实现类使用LinkedList,适合FIFO
*
* Queue也有很多其他的实现类,比如java.util.concurrent.LinkedBlockingQueue。
* LinkedBlockingQueue是一个阻塞的线程安全的队列,底层实现也是使用链式结构。
*/
public class TestQueue {
// 定义一个队列
Queue<String> queue;
@Before
public void before() {
// 实例化队列变量
queue = new LinkedList<String>();
// add方法向队列中添加元素,返回布尔值,add方法添加失败时会抛异常,不推荐使用
// queue.add("1");
// offer方法向队列中添加元素,返回布尔值
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offer("d");
}
@Test
public void test1() {
// poll方法移除队列首个元素并返回,若队列为空,返回null
String pollEle = queue.poll(); // 弹出元素,先进先出,弹出了a
System.out.println(pollEle); // a
System.out.println(queue); // [b, c, d, e]
// peek方法返回队列首个元素,但不移除,若队列为空,返回null
String peek = queue.peek(); // 查看首个元素
System.out.println(peek);
System.out.println(queue); // [b, c, d, e]
}
}