本文为对List集合的再一次整理,从父集接口Collection到顶级接口Iterable再到线程不安全实现类:ArrayList、LinkedList,再到线程安全实现类:Vector(被弃用)、CopyOnWriteArrayList。java
List集合扩展了Collection接口,它是一个容许重复的集合,即容许有多个元素引用相同的对象。
咱们来看看List接口的源码:git
public interface List<E> extends Collection<E> { void add(int index,Object ele)// 根据下标添加元素 Boolean addAll(int index,Collection eles) // 在下标index插入另外一个集合的所有元素 Object get(index) // 获取对应下标的元素 int indexOf(Object ele) // ele在集合中首次出现的位置 若是不存在,返回-1 int lastIndexOf(Obj ele) // ele在集合中最后出现的位置 若是不存在,返回-1 Object remove(int index) 移除指定位置索引的元素 Object set(int index,Object ele); // 在下标在下标index插入一个元素 }
能够看到List接口继承了Collection接口,那么接下来看看Collection接口github
在java类库中,Collection接口是集合类的基本接口,这个接口有两个基本的方法:web
public interface Collection<E> extends Iterable<E> { boolean add(E element); Iterator<E> iterator(); ... }
add方法用于向集合中添加元素。若是添加元素确实改变了集合就返回true,若是集合没有发生改变就返回false。(其实后面具体的实现类都会对该方法进行重写)列如,向一个集合中添加一个已经存在的对象,这个添加请求就没有效果会返回false,由于Set集合不容许重复对象。从代码中能够看到, Collection接口还包括了一个iterator()方法,返回类型为Iterator接口对象,那么什么Iterator接口?数组
Collection接口中的其它方法:安全
//添加方法: add(Object o) //添加指定元素 addAll(Collection c) //添加指定集合 //删除方法: remove(Object o) //删除指定元素 removeAll(Collection c) //输出两个集合的交集 retainAll(Collection c) //保留两个集合的交集 clear() //清空集合 //查询方法: size() //集合中的有效元素个数 toArray() //将集合中的元素转换成Object类型数组 //判断方法: isEmpty() //判断是否为空 equals(Object o) //判断是否与指定元素相同 contains(Object o) //判断是否包含指定元素 containsAll(Collection c) //判断是否包含指定集合
咱们还知道有一个Collections,它是一个包装类。它包含有各类有关集合操做的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。数据结构
翻看Iterable源码:并发
public interface Iterable<T> { /** * Returns an iterator over elements of type {@code T}. * * @return an Iterator. */ Iterator<T> iterator(); }
能够看到在Iterable接口中定义了一个iterator方法返回类型为Iterator,继续跟进这个Iterator:框架
public interface Iterator<E> { boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }
很明显的能够看到,这个Iterator接口就像当于链表中的结点,只不过在C语言里结点里的指针在java中变成了对象的引用。那么 咱们就应该知道,经过反复的调用nest()就能够逐个的访问集合中的每一个元素,可是当到达了集合的末尾,nest方法将抛出一个NoSuchElementException。所以,每次都用next方法前都应该调用hasNext方法进行判断。hasNext方法的做用是判断对象是否还有下一个元素,有就返回true,不然返回false。remove方法会删除上次调用next方法时返回的元素。就像是删除一个元素以前先看下它是颇有必要的,remove方法就是按照这个理念设计的。举一个访问集合中全部元素的案例:dom
Collection<String> s = new ArrayList<String>(); s.add("xiaohong"); s.add("xionming"); s.add("wanger"); Iterator<String> iterator = s.iterator(); while (iterator.hasNext()) { String element = iterator.next(); System.out.println(element);
在Java SE8版本中,新加入了for each循环遍历,编译器简单地将“for each”循环翻译为带有迭代器的循环。
for (String string : s) { System.out.println(string); }
显然,经过"for each"遍历使得代码更加简洁,全部实现了Iterable接口的对象均可以使用"for each"循环。Collection接口扩展了Iterable接口。
好了,到这里咱们就看完了List的父级接口了,接下来咱们看看它的实现类。
若是实现了Collection接口的每个类都要实现它的全部方法,那么将是一件很烦的事情。此时,AbstractList应运而生。它将基础的iterator抽象化,其它的方法给实现了,此时一个具体的集合类就能够扩展AbstractList类,而且只需提供Iterator方法,固然若是不满意AbstractList类实现的方法也能够在子类重写它的方法。
咱们先来看看ArrayList的源码:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ... private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; ... public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 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); } } }
经过源码咱们能够看到:
咱们再看看ArrayList是如何扩容的,咱们跟进add():
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private int size; private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; ... public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; // 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 + (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); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } }
分析:
LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操做,另外它实现了Deque接口,使得LinkedList类也具备队列的特性; LinkedList不是线程安全的,若是想使LinkedList变成线程安全的,能够调用静态类Collections类中的synchronizedList方法。
须要提一下的是,LinkedList类中有一个ListIterator listIterator方法,listIterator接口中包含一个add方法:
public interface ListIterator<E> extends Iterator<E> { boolean hasNext(); E next(); boolean hasPrevious(); void set(E e); void add(E e); }
由于链表是一个有序的集合,每一个对象的位置就显得十分重要。LinkedList中的add方法只能将对象添加到链表尾部,而常常却要将对象添加到链表的中间,迭代器就是用于描述集合中的位置的,因此这种依赖位置的方法就交由迭代器来完成。由于Set集合是无序的,因此在Iterator接口中就没有add方法,而是扩展了一个LinkIterator接口来实现。
值得一提的是:你们都知道,链表是不支持快速随机访问的。若是要查看链表中的第n个元素,就必须从头开始,越过n-1个元素,没有捷径可走,但尽管如此,LinkedList仍是提供了一个用来访问某个特定元素的get方法,固然这个方法的效率并不高,若是在使用这个方法,那么可能对于所要解决的问题使用了错误的数据结构。LinkedList类中get方法所谓的随机访问都是须要从列表的头部开始搜索,效率极低。使用链表的惟一理由是尽量的减小在链表中间插入或删除元素所付出的代价。
LinkedList中特有的方法
//查询方法: getFirst() //获取集合中的第一个元素 getLast() //获取集合中的最后一个元素 //添加方法: addFirst(Object o) //在集合的第一个位置添加指定元素 addLast(Object o) //在集合的最后一个位置添加指定元素 //删除方法: removeFirst() //删除集合中的第一个元素 removeLast() //删除集合中的最后一个元素
下面程序简单的建立了两个链表,将它们合并在一块儿,而后从第二个链表中每隔一个元素删除一个元素,最后测试removeAll()方 法 :
package listdemo; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; public class LinkedListTest { public static void main(String[] args) { List<String> a = new LinkedList<>(); a.add("aaa"); a.add("bbb"); a.add("eee"); List<String> b = new LinkedList<>(); b.add("AAA"); b.add("BBB"); b.add("EEE"); ListIterator<String> aIter = a.listIterator(); ListIterator<String> bIter = b.listIterator(); //a集合合并b集合 while(bIter.hasNext()) { if(aIter.hasNext()) aIter.next(); aIter.add(bIter.next()); } System.out.println(a); //从b链表中每间隔一个元素删除一个元素 while(bIter.hasNext()) { bIter.next();//跳过一个元素 if(bIter.hasNext()) { bIter.next(); bIter.remove();//先查后删 } } System.out.println(b); //测试删除全部 a.removeAll(a); System.out.println(a); } }
public class ListTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 30; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(1,8)); System.out.println(list); },Integer.toString(i)).start(); } } }
运行结果:报java.util.ConcurrentModificationException异常。
致使缘由:并发争抢修改致使
如何解决?有三种方法:
vector类和ArrayList类的差异就是Vector在每一个方法前都加了个sychronized锁,其它地方和ArrayList基本一致,因为性能太差,基本已被弃用。
CopyOnWrite的意思:在计算机,若是你想要对一块内存进行修改时,咱们不在原有内存块中进行写操做,而是将内存拷贝一份,在新的内存中进行写操做,写完以后再将指向原来内存的指针指向新的内存,原来的内存就能够等着被GC回收了。
再来看看源码:
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { final transient ReentrantLock lock = new ReentrantLock(); private transient volatile Object[] array; ... public CopyOnWriteArrayList() { setArray(new Object[0]); } final void setArray(Object[] a) { array = a; } 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(); } } public E get(int index) { return get(getArray(), index); } final Object[] getArray() { return array; } }
经过源码咱们能够看到:
最后附上在最开始说的ArrayList线程不安全使用CopyOnWriteArrayList解决的代码:
public class CopyOnWriteArrayListTest { public static void main(String[] args) { List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 30; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(1,8)); System.out.println(list); },Integer.toString(i)).start(); } } }
本文所涉及的全部代码都在个人GitHub上:https://github.com/dave0824/jmm