一、ArrayList
1、Arraylist 类定义
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
- ArrayList 继承 AbstractList(这是一个抽象类,对一些基础的 List 操作进行封装);
- 实现 List,RandomAccess,Cloneable,Serializable 几个接口;
- RandomAccess 是一个标记接口,用来表明其支持快速随机访问,为Lis提供快速访问功能的;实现了该接口的list可以通过for循环来遍历数据,比使用迭代器的效率要高;
- Cloneable 是克隆标记接口,覆盖了 clone 函数,能被克隆;
- java.io.Serializable 接口,ArrayList 支持序列化,能通过序列化传输数据。
2、构造方法
// 默认构造函数
ArrayList()
// capacity是ArrayList的默认容量大小,当由于增加数据导致容量不足时,容量会增加到当前容器的1.5倍
ArrayList(int capacity)
// 创建一个包含collection的ArrayList
ArrayList(Collection<? extends E> collection){
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
上面包含Collection参数的构造方法里面有个数字:6260652,这是一个Java的bug,意思当给定的集合内的元素不是Object类型时,会转换成Object类型。一般情况下不会触发此 bug 6260652,只有在下面的场景下才会触发:ArrayList初始化之后(ArrayList元素非Object类型),再次调用toArray方法时,得到Object数组,并且往Object数组赋值时才会触发该bug:
List<String> list = Arrays.asList("hello world", "Java");
Object[] objects = list.toArray();
System.out.println(objects.getClass().getSimpleName());
objects[0] = new Object();
输出结果:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Object
at com.jolly.demo.TestArrayList.main(TestArrayList.java:23)
String[]
上面bug在jdk9后解决了:Arrays.asList(x).toArray().getClass() should be Object[].class
3、成员变量
-
transient Object[] elementData
Object 数组,Arraylist 实际存储的数据。 如果通过不含参数的构造函数ArrayList()来创建ArrayList,默认为空数组
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
ArrayList无参构造器初始化时,默认是空数组的,数组容量也是0,真正是在添加数据时默认初始化容量为10;
-
为什么数组类型是 Object 而不是泛型E:如果是E的话在初始化的时候需要通过反射来实现,另外一个泛型是在JDK1.5之后才出现的,而ArrayList在jdk1.2就有了;
-
①、为什么 transient Object[] elementData;
ArrayList 实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化 99 个 null 元素.为了保证在序列化的时候不
会将这么多 null 同时进行序列化, ArrayList 把元素数组设置为 transient; -
②、为什么要写方法:
writeObject and readObject
:前面提到为了防止一个包含大量空对象的数组被序列化,为了优化存储.所以,ArrayList 使用 transient 来声明elementData作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写writeObject 和 readObject方法的方式把其中的元素保留下来writeObject方法把elementData数组中的元素遍历的保存到输出流ObjectOutputStream)中。readObject方法从输入流(ObjectInputStream)中读出对象并保存赋值到elementData数组中
-
-
private int size
:Arraylist 的容量 -
protected transient int modCount = 0
这个为父类 AbstractList 的成员变量,记录了ArrayList结构性变化的次数在ArrayList的所有涉及结构变化的方法中都增加modCount的值,AbstractList 中的iterator()方法(ArrayList直接继承了这个方法)使用了一个私有内部成员类Itr,该内部类中定义了一个变量 expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值,在next()方法中调用了checkForComodification()方法,进行对修改的同步检查;
4、新增和扩容
新增元素主要有两步:
- 判断是否需要扩容,如果需要执行扩容操作;
- 直接赋值
public boolean add(E e) {
//确保数组大小是否足够,不够执行扩容,size 为当前数组的大小
ensureCapacityInternal(size + 1); // Increments modCount!!
//直接赋值,线程不安全的
elementData[size++] = e;
return true;
}
如果数组容量不够,对数组进行扩容,JDK7 以后及 JDK7 前的实现不一样。扩容本质上是对数组之间的数据拷贝;
- JDK6:直接扩容,且扩容一般是源数组的 1.5 倍:
int newCapacity = (oldCapacity * 3)/2 + 1;
- JDK7:扩容,且一般是之前的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1); // 拷贝数组 elementData = Arrays.copyOf(elementData, newCapacity);
- JDK8:扩容,一般是之前的1.5倍
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++; // 如果我们期望的最小容量大于目前数组的长度,那么就扩容 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); }
5、ArrayList 安全隐患
-
当传递 ArrayList 到某个方法中,或者某个方法返回 ArrayList,什么时候要考虑安全隐患?如何修复安全违规这个问题呢?
当array被当做参数传递到某个方法中,如果array在没有被复制的情况下直接被分配给了成员变量,那么就可能发生这种情况,即当原始的数组被调用的方法改变的时候,传递到这个方法中的数组也会改变。下面的这段代码展示的就是安全违规以及如何修复这个问题:
- ArrayList 被直接赋给成员变量——安全隐患:
public void setMyArray(String[] myArray){ this.myArray = myArray; }
- 修复代码
public void setMyArray(String[] newMyArray){ if(newMyArray == null){ this.myArray = new String[0]; }else{ this.myArray = Arrays.copyOf(newMyArray, newMyArray.length); } }
- ArrayList 被直接赋给成员变量——安全隐患:
-
线程安全问题的本质
因为ArrayList自身的elementData、size、modCount在进行各种操作时,都没有加锁,而且这些数据并不是可见的(volatile),如果多个线程对变量进行操作时,可能存在数据被覆盖问题;
二、Vector
1、签名:
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
2、方法与变量:
大致同 Arraylist,只是 Vector 的方法都是同步的,其是线程安全的
3、Vector 多一种迭代方式
Vector vec = new Vector();
Integer value = null;
Enumeration enu = vec.elements();
while (enu.hasMoreElements()) {
value = (Integer)enu.nextElement();
}
三、面试题
1、ArrayList 与 Vector
1.1、区别
1.2、关于Vector线程安全
(Vector 是同步的.Vector 是线程安全的动态数组.它的操作与 ArrayList 几乎一样)如果集合中的元素的数目大于目前集合数组的长度时,Vector 增长率为目前数组长度的 100%,而 Arraylist 增长率为目前数组长度的 50%.如过在集合中使用数据量比较大的数据,用 Vector 有一定的优势
注意:在某些特殊场合下,Vector并非线程安全的,看如下代码
public static void main(String[] args) {
Vector<Integer> vector = new Vector<>();
while (true) {
for (int i = 0; i < 10; i++) {
vector.add(i);
}
Thread t1 = new Thread() {
public void run() {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
}
};
Thread t2 = new Thread() {
public void run() {
for (int i = 0; i < vector.size(); i++) {
vector.get(i);
}
}
};
t1.start();
t2.start();
}
}
上述代码会抛出异常:java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9
2、ArrayList的sublist修改是否影响list本身
-
方法实现
// fromIndex: 集合开始的索引,toIndex:集合结束的索引,左开右闭 public List<E> subList(int fromIndex, int toIndex) { // 边界校验 subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); } private class SubList extends AbstractList<E> implements RandomAccess { private final AbstractList<E> parent; // parent的具体实现类是 ArrayList private final int parentOffset; private final int offset; int size; SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) { this.parent = parent; this.parentOffset = fromIndex; this.offset = offset + fromIndex; this.size = toIndex - fromIndex; this.modCount = ArrayList.this.modCount; } }
subList 可以做集合的任何操作
-
调用该方法后的生成的新的集合的操作都会对原集合有影响,在subList集合后面添加元素,添加的第一个元素的位置就是上述toIndex的值,而原始集合中toIndex的元素往后移动。其add方法调用过程:
add(element) --> AbstractList.add(e) --> SubList.add(index, e) --> parent.add(index + parentOffset, e) --> ArrayList.add(newIndex, e)
3、为什么最好在newArrayList的时候最好指定容量?
4、SynchronizedList、Vector有什么区别
- SynchronizedList 是java.util.Collections的静态内部类;Vector是java.util包中的一个类;
- 使用add方法时,扩容机制不一样;
- SynchronizedList有很好的扩展和兼容功能,可以将所有的List的子类转成线程安全的类;
- 使用SynchronizedList的时候,进行遍历时需要手动进行同步处理;
- SynchronizedList可以指定锁的对象
5、Arrays.asList(T…args)获得的List特点
- 其返回的List是Arrays的一个内部类,是原来数组的视图,不支持增删操作;
- 如果需要对其进行操作的话,可以通过ArrayList的构造器将其转为ArrayList;
6、Iterator和ListIterator区别
- 都是用于遍历集合的,Iterator可以用于遍历Set、List;ListIterator只可用于List;
- ListIterator实现的Iterator接口;
- ListIterator可向前和向后遍历;Iterator只可向后遍历;