ArrayList 实现了List接口,是List的一个具体实现类,底层结构是可变数组,在使用无参构造方法初始化ArrayList时,默认容量为10,扩容倍数为1.5。ArrayList实现了RandomAccess、Serializable、Cloneable、Iterable接口,表明其支持随机访问、序列化与反序列化、克隆以及通过迭代器访问集合。
与LinkedList相比,因为ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问,也就是能够下标进行快速访问,也表明底层是数组,因此ArrayList的查询效率比LinkedList高。但是涉及到元素的添加和删除,LinkedList的效率是比ArrayList高的。
使用空构造方法创建ArrayList并不会创建数组,等真正add的时候会真正的创建数组。
此外,Arraylist和LinkedList都是线程不安全的。如果想使用线程的List,可以参考Vector以及CopyOnWriteArrayList。
使用ArrayList arrayList = new ArrayList<>(); 只是创建一个空数组。
使用add方法时,会进入到ensureCapacityInternal,此时最小容量为当前的size + 1 :
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); }如果当前数组为空,则最小容量为10,否则为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); }newCapacity是旧数组的Capacity的1.5倍。
如果newCapacity - minCapacity < 0,说明原本是空数组,则newCapacity等于默认容量为10。
如果newCapacity大于数组的最大容量MAX_ARRAY_SIZE的话,那么newCapacity就等于MAX_ARRAY_SIZE。
确定好newCapacity之后,使用Arrays.copyOf进行复制,并将旧数组的引用指向新数组:
elementData = Arrays.copyOf(elementData, newCapacity);随机访问接口,可以看下源码发现居然没有一行代码:
RandomAccess接口这类没有一行代码的接口也叫做标记接口,意思是表明实现该接口的类支持随机访问,那什么叫随机访问呢?简单来说就是可以靠下标index快速访问,换言之也表明该类的底层数据结构是数组。
与之相对的LinkedList并没有实现RandomAccess接口,表明他不支持下标访问,也表明其底层数据结构不是数组:
那我们应该怎么利用RandomAccess接口呢?可以看下下面这个例子:
import java.util.*; public class RandomAccessTest { public static void traverse(List list){ if (list instanceof RandomAccess){ System.out.println("实现了RandomAccess接口,不使用迭代器"); for (int i = 0;i < list.size();i++){ System.out.println(list.get(i)); } }else{ System.out.println("没实现RandomAccess接口,使用迭代器"); Iterator it = list.iterator(); while(it.hasNext()){ System.out.println(it.next()); } } } public static void main(String[] args) { List<String> arrayList = new ArrayList<>(); arrayList.add("a"); arrayList.add("b"); traverse(arrayList); List<String> linkedList = new LinkedList<>(); linkedList.add("c"); linkedList.add("d"); traverse(linkedList); } }可以用instanceof判断该类有没有实现RandomAccess接口。
如果实现了RandomAccess接口,则可以使用for循环的形式遍历得到该集合。
如果没有实现RandomAccess接口,则使用迭代器模式。
此外可以做个实验,实验是ArrayList和LinkedList都使用for循环下标、迭代器迭代计算时间,我这里就只放结果了:
无论是Arraylist还是LinkedList都实现了Cloneable接口,表明能够克隆。该方法也是一个标记型接口。
在此之前,可以想一下在Object中也有Clone方法,直接使用Clone方法进行克隆不行吗?
可以看到,在该类没有实现Cloneable接口时,会抛出CloneNotSupportedException异常:
因此Clone类需实现Cloneable接口,既然说到了Clone,那就引申一下跟Clone相关的内容。
在默认情况下,实现Cloneable接口并重写Clone方法,指的是浅拷贝。
浅拷贝:基本数据类型复制一份,但是引用类型没有复制,拷贝对象中的引用类型指针是指向原对象的指针,如果修改浅拷贝复制出来的对象,那么原对象也会受到变化。
深拷贝:全面复制,彼此修改不影响。
相关博客:https://blog.csdn.net/jeffleo/article/details/76737560
具体见ArrayList 序列化与反序列化
具体见ArrayList currentModifyException
在Java.util包下的集合都是快速失败的,用于快速判断当前集合是不是在并发情况下修改(有一个比较经典的例子就是单线程中集合迭代器进行遍历时用list.remove修改),主要涉及到的变量就是modCount,modCount描述的是数组结构变化的次数,结构变化意思是数组添加元素、删除元素或者数组大小发生变化时,该值就会+1。
如果用迭代器迭代或者序列化时,都会比较modCount 跟 expectModcount进行比较。
在迭代器遍历集合时,如果操作不当,就会抛出著名的ConcurrentModificationException。
public static void main(String[] args) { ArrayList arrayList = new ArrayList(); for (int i = 0; i < 10; i++) { arrayList.add(i); } remove(arrayList); System.out.println(arrayList); } public static void remove(ArrayList<Integer> list) { Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer number = iterator.next(); if (number % 2 == 0) { // 抛出ConcurrentModificationException异常 list.remove(number); } } }首先先看一下iterator的next方法:
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]; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }!! 在iterator.next的时候会去判断当前modCount 跟 expectedModeCount是否相等,Iterator刚创建时,会将当前的list的modCount赋值给expectModCount,每次next的时候都会去判断checkForComodification,使用list.remove的时候,modCount会+1,下一次next的时候就会出现modCount != expectedModCount的情况,抛出异常。
正确的使用方法应该是:
it.remove(); public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; // 移除之后将modCount 重新赋值给 expectedModCount expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }在JUC包下的集合都是属于安全失败的,也就是在遍历的时候不是在原本的集合上遍历的,而是先复制一份集合,在集合的副本中进行遍历(CopyOnWriteArrayList)。
虽然ArrayList实现了Serializeable接口,但是他的elementData却用transient修饰,transient表示序列化时不序列化改项:
但是他的序列化方法中把size,以及实际中存储的数据都持久化,从而节省时间和空间。
/** * 将ArrayLisy实例的状态保存到一个流里面 */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // 按照顺序写入所有的元素 for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }多线程可以使用Vector,因为Vector是线程同步的,但是每次执行都要加锁,开销比较大,而且Vector扩容是Arraylist的2倍。
但是Vector一定是线程安全的吗? 答案是否定的。
比如这个方法,size和remove都是线程安全的,但是deleteLast不是线程安全的,因为deleteLast不是整体原子性,会出现ArrayIndexBoundOfException。
返回的是Collections工具里的一个静态内部类,里面的方法都是用synchronized修饰的。
CopyOnWriteArrayList是基于写时复制,做到读写不冲突的一个线程安全的ArrayList。 首先写的时候会使用ReentranLock来加锁,加锁的目的是为了复制一份变量的副本来写数据,避免产生多个变量副本。
那么在读的时候就很简单了,直接读取,不用加锁。
但是CopyOnWriteArrayList如果数组元素特别多的情况下,那么复制的时候内存大小就会变为两倍,这个是要注意的一个问题。
https://mp.weixin.qq.com/s/3PNWmtS-bEZgZjd9wyMiDA https://mp.weixin.qq.com/s/WoGclm7SsbURGigI3Mwr3w