ArrayList、LinkedList和Vector的异同和底层源码解析?
相同点:从源码上看ArrayList
、LinkedList
和Vector
都实现了List接口,存储数据的特点相同:存储的数据有序
、可重复
不同点:
ArrayList
:是List的接口的主要实现类,在jdk1.2才更新出来,线程不安全,效率高,查询快,增删慢,底层使用的是Object []LinkedList
:在jdk1.2才更新出来,线程不安全,对于频繁的插入、删除操作,使用此类效率高于ArrayListVector
:是List的接口的古老实现类,在jdk1.0就已经有了,方法被synchronize修饰,线程安全,相对而言,效率低,底层也是Object []
ArrayList
:底层是一个被transient修饰的Object []数组
transient Object[] elementData;
这里看到Object[] 数组被transient
修饰,这里解释一下transient的作用:
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
Vector
:底层是一个被 protected修饰的Object []数组
protected Object[] elementData;
这里看到Object[] 数组被protected
修饰,这里解释一下protected的作用:
正如字面意思,proctected“受保护的
”。所以,被proctected关键字修饰的成员也自然是“被保护”的,其只对本包及其子类可见
。
- 父类的proctected成员包内可见,并且对子类可见
- 子类父类不在同一个包里的话子类只能访问从父类继承的proctected方法。
LinkedList
:底层是双向链表
transient Node<E> first;//头节点
transient Node<E> last;
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
注意:List接口也是jdk1.2才更新出来,然后把Vector
这个类归结到List接口实现类下
ArrayList底层源码分析:
ArrayList的在jdk7
和jdk8
中各有所不同,但是他们的底层使用的是Object [] 数组
是不变的
1.这里先看jdk7
中开始看
1.1这里先看jdk7
中开始看,先从无参构造开始查看
public ArrayList() {
this(10);
}
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
从源码可以看到,ArrayList
的无参构造中调用this(10)
方法,调用ArrayList的有参构造,创建了一个Objecy[]数组,并添加了默认初始化容量为10
1.2 ArrayList list=new ArrayList();
//底层创建了长度为10的Objects[] 数组
list.add(123);//elemnt[0]=new Integer(123);
.....
list.add(11);
//如果此次添加的元素导致element数组容量不够,则需要扩容。
默认情况下,扩容为原来的容量的1.5倍
,同时需要把原有的数组中的数据复制到新的数组中
//添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
从源码中,可以看出添加元素时,先确保数组容量是否满足需要,调用ensureCapacityInternal()
方法,进行校验,点开ensureCapacityInternal()
方法进行查看:
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
发现ensureCapacityInternal()
方法判断当前容量+1后的长度-数组的长度是否大于0,如果大于0证明数组长度还够,就会跳出该方法,如果小于0,则进入grow
方法,点开grow
方法查看:
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);
}
从grow()
方法中可以看出,对当前的elementData
数组进行扩容,扩容为oldCapacity + (oldCapacity >> 1);
,也就是原数组长度+原数组长度/2,也就是扩容为原来数组长度的1.5倍
,并调用Arrays.copyOf()方法
把之前的数组中的数据复制到新的数组中。
2.从看jdk8
中再看ArrayList
先看ArrayList中的几个变量:
//定义一个空数组以供使用
private static final Object[] EMPTY_ELEMENTDATA = {};
//也是一个空数组,跟上边的空数组不同之处在于,这个是在默认构造器时返回的,扩容时需要用到这个作判断,后面会讲到
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放数组中的元素,注意此变量是transient修饰的,不参与序列化
transient Object[] elementData;
//数组的长度,此参数是数组中实际的参数,区别于elementData.length,后边会说到
private int size;
2.1 从无参构造开始查看
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] 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);
}
}
从源码中发现,在jdk8
中,ArrayList并没有给默认的初始化容量,而是给了一个常量,这个常量是一个空的Object数组。相对于jdk7
来说,没有实例话对象,减少了一定的内存的压力.
2.2ArrayList list=new ArrayList();
//底层创建了Object [] element={}
list.add(123);//elemnt[0]=new Integer(123);
//第一次调用add()
的时候,才会创建长度为10的Object数组
.....
list.add(11);
//如果此次添加的元素导致element数组容量不够,则需要扩容。
默认情况下,扩容为原来的容量的1.5倍
,同时需要把原有的数组中的数据复制到新的数组中
再看添加元素add()
的操作
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
跟jdk7
类似,确保数组容量,点进去ensureCapacityInternal()
方法查看
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
发现这里新添加了一个calculateCapacity
方法,因为在jdk8
中没有默认的初始化长度,所以需要来获取minCapacity
长度,如果ArrayList是以无参构造的形式创建,elementData={},而如果是以有参构造的形式创建,elementData=new Object [initialCapacity]
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
从calculateCapacity()
方法发现,如果elementData为空时,这里把minCapacity
跟DEFAULT_CAPACITY
进行对比,最终会返回10
**注意:**如果创建ArrayList用的无参构造,创建ArrayList时调用的是无参构造,此方法会返回DEFAULT_CAPACITY(值为10)和minCapacity的最大值,因此,最终会返回固定值10;若创建ArrayList时调用了有参构造,则此方法会返回1,注意这个minCapacity变量只是第一次调用add方法时值为1,此后的调用需要根据实际的数组长度size+1.
然后调用ensureExplicitCapacity
方法
private static final int DEFAULT_CAPACITY = 10;
modCount++用到了快速失败机制,此处先不做讨论
再回到ensureCapacityInternal
方法中,点开ensureExplicitCapacity()
方法查看
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这里就跟jdk7
一样,判断容量是否满足,不满足就调用grow()
方法,进行扩容
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);
}
从grow()
方法中可以看出,对当前的elementData
数组进行扩容,扩容为oldCapacity + (oldCapacity >> 1);
,也就是原数组长度+原数组长度/2,也就是扩容为原来数组长度的1.5倍
,并调用Arrays.copyOf()方法
把之前的数组中的数据复制到新的数组中。
LinkedList底层源码分析:LinkedList没有扩容机制,因为是链表
1.先看LinkedList
内部的属性变量
//记录集合中元素的个数
transient int size = 0;
//链表中的头节点
transient Node<E> first;
//链表中的尾节点
transient Node<E> last;
Node
为LinkedList
的内部类,通过这个Node可以看出这个LinkedList是一个双向链表
private static class Node<E> {
E item;
//下一个节点
Node<E> next;
//上一个节点
Node<E> prev;
//构造就是上一个节点,要添加的元素,下一个节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2.LinkedList list=new LinkedList();
内部声明了Node
类型的first
和last
属性,默认值为null
list.add(111);
将111封装到Node
中,创建了Node对象,这里111自动装箱为new Integer(111)
被添加list集合中
public boolean add(E e) {
linkLast(e);
return true;
}
在add()
方法中,如果没有制定添加某个位置,默认是在末尾添加元素,所以调用了linkLast(e);
方法
void linkLast(E e) {
//先把最后一个元素的值暂时取出
final Node<E> l = last;
//创建一个node节点,为新添加的元素,他的有参构造为 Node(Node<E> prev, E element, Node<E> next)
//因为不知道新添加的元素的下一个节点是谁,所以默认为null,如果是第一次添加l也是为null的
//final Node<E> newNode = new Node<>(null, e, null);
final Node<E> newNode = new Node<>(l, e, null);
//把新添加的元素作为最后一个元素
last = newNode;
//判断是否是第一次添加,如果是,就把头节点为当前新添加的元素节点,也就是newNode
//如果不是第一次添加,就更改头节点的下一个元素节点为当前新添加的元素节点
if (l == null)
first = newNode;
else
l.next = newNode;
//集合中的元素数量+1
size++;
//快速失败机制,暂不讨论
modCount++;
}
list.remove(111)
如果要删除111的元素,调用remove
方法,因为111
为整形,会调用remove
的删除制定索引下的元素的方法
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
从remove中可以看出,删除之前先调用checkElementIndex
方法,检查元素索引是否超出,超出就抛IndexOutOfBoundsException
异常
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
点开isElementIndex
方法,判断删除的索引是否大于等于零,并且小于当前集合的元素个数
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
删除元素的方法unlink
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
如需删除111
的元素,需要如下写法
list.remove(new Integer(111));
这样就会调用删除元素为111
的元素了,这里也是调用了unlink
方法
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
Vector底层源码分析:方法被synchronize
修饰,效率低一点,但是线程安全,虽然线程安全,但是在实际开发中,Vector也不会怎么用到,如果用到ArrayList的时候,可以用Collections.synchronizedList()
方法可以返回线程安全的List
集合
先查看Vector的属性变量
//存储元素的数组
protected Object[] elementData;
//集合中元素的个数
protected int elementCount;
//用于扩容时进行比较的
protected int capacityIncrement;
Vector vector=new Vector();
底层是数组,无参构造初始化时,默认为容量为10的Object数组
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
vector.add("aaa");
发现add
方法被synchronize
修饰,效率会低,但是线程安全
public synchronized boolean add(E e) {
//快速失败机制,暂不讨论
modCount++;
//确保容量满足
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
点开ensureCapacityHelper
方法查看
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
发现ensureCapacityHelper
方法跟ArrayList一样,点开grow
方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//因为capacityIncrement初始化为0,所以不满足大于0的需求,所以这里时oldCapacity +oldCapacity
//也就是集合容量扩容为原来的2倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
发现capacityIncrement
这个属性大于0时,返回capacityIncrement
,因为初始化时为0,所以这里返回固定值为2倍
面试题:请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层是什么?扩容机制?Vector和ArrayList的最大区别?
-
ArrayList和LinkedList的异同
二者都线程不安全,相对线程安全的Vector,执行效率高。
此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。
-
ArrayList和Vector的区别
Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。