0. 前言

面试的时候常会被问到ArrayList和Vector的区别。要回答这个问题,首先得对ArrayList的源码进行彻底理解,带着这个问题开始今天的文章。本文原创,转载请注明出处:SEU_Calvin的博客

先对ArrayList的特性进行一个概述:

(1)ArrayList内部使用数组来保存元素。可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。

(2) ArrayList 实现了RandomAccess, Cloneable, java.io.Serializable三个标记接口,表示它自身支持快速随机访问,克隆,序列化。

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

(3)ArrayList中允许元素为null,因为我们会在后面的源码解析中看到查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理。

(4) ArrayList 不具备并发访问特性,因为所有的方法没有加锁机制。 
(5)如果不指定容量大小,默认情况下ArrayList容量为10,在JDk1.7中ArrayList最大容量为 Integer.MAX_VALUE - 8。并且ArrayList 内部具备自动扩容机制,当容量不足时,会自动申请内存空间。

1.  ArrayList初始化

private transient Object[] elementData;
//elementData中已存放的元素的个数,而不是elementData的容量
private int size; public ArrayList(int paramInt) {
if (paramInt < 0)
throw new IllegalArgumentException("Illegal Capacity: " + paramInt);
this.elementData = new Object[paramInt];
}
public ArrayList() {
this(10);
} //两种常用的构造方式
List<String> list = new ArrayList<String>( );//默认构造大小为10
List<String> list = new ArrayList<String>(5);//自定义大小

elementData是用于保存具体元素的数组,构造方法中指定的大小是elementData数组的长度,即初始化开辟一定数量的内存,并不是list.size()即元素个数的值。

elementData属性采用了transient来修饰,表明其不使用Java默认的序列化机制来实例化,但是该属性是ArrayList的底层数据结构,在网络传输中一定需要将其序列化,之后使用的时候还需要反序列化,那不采用Java默认的序列化机制,那采用什么呢?直接翻到源码的最下边有两个方法,发现ArrayList自己实现了序列化和反序列化的方法。

/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that is,serialize it).
* @serialData The length of the array backing the <tt>ArrayList</tt>
*instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
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 array length
s.writeInt(elementData.length); // Write out all elements in the proper order.
for (int i = 0; i < size; i++)
s.writeObject(elementData[i]); if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
} } /**
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject(); // Read in array length and allocate array
int arrayLength = s.readInt();
Object[] a = elementData = new Object[arrayLength]; // Read in all elements in the proper order.
for (int i = 0; i < size; i++)
a[i] = s.readObject();
}


2.  ArrayList的add操作

第一种是在数组尾部加入要加入的数据:

public boolean add(E e) {
ensureCapacity(size + 1);//确保对象数组elementData有足够的容量
elementData[size++] = e;//加入新元素e,size加1
return true;
}

第二种当然也可以在指定位置add进数据:

public void add(int index, E element) {
//检查index是否在已有的数组中
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
ensureCapacity(size + 1);//确保对象数组elementData有足够的容量
  //将index及其后边的所有的元素整块后移,空出index位置 
System.arraycopy(elementData, index, elementData, index+1, size-index);
elementData[index] = element;//插入元素
size++;//已有数组元素个数+1
}


3.  ArrayList的扩容操作

在add方法里,会看到ensureCapacity()方法的出现,作用是确保数组的容量足够存放新加入的元素,若不够,就会进行扩容操作:

public void ensureCapacity(int minCapacity) {
modCount++; //记录这个list被修改的次数
int oldCapacity = elementData.length;//获取数组容量
//当数组满了,又有新元素加入的时候,执行扩容逻辑
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3) / 2 + 1;//新容量为旧容量的1.5倍+1
//如果扩容后的新容量还是没有传入的所需的最小容量大或等于
//主要发生在addAll(Collection<? extends E> c)中
if (newCapacity < minCapacity)
newCapacity = minCapacity;//新容量设为最小容量
elementData = Arrays.copyOf(elementData, newCapacity);//复制新容量
}
}

在上述代码的扩容结束后,调用了Arrays.copyOf(elementData,newCapacity)方法,这个方法中先创建了一个新的容量为newCapacity的对象数组,然后使用System.arraycopy()方法将旧的对象数组复制到新的对象数组中。新数组大小为原来的1.5倍加1,arraycopy 为System类中的native方法,有比较大的性能开销,所以在我们使用ArrayList时,最好能预计数据的数量并在第一次创建时就申请够内存,否则建议使用LinkedList。如下所示:

//在第一次创建时就申请够内存
ArrayList list = new ArrayList();
list.ensureCapacity(N);

此外,ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。

它可以通过trimToSize方法来实现。代码如下:

public void trimToSize()  {
this.modCount += 1;
int i = this.elementData.length;
if (this.size < i)
this.elementData = Arrays.copyOf(this.elementData, this.size);
}

4.  ArrayList的set操作

/**
* 更换特定位置index上的元素为element,返回该位置上的旧值
*/
public E set(int index, E element) {
RangeCheck(index);//检查索引范围
E oldValue = (E) elementData[index];//旧值
elementData[index] = element;//该位置替换为新值
return oldValue;//返回旧值
}
//那么根据某个下标值来取值的方法也就很简单了:
public E get(int index) {
RangeCheck(index);//检查索引范围
return (E) elementData[index];//返回元素,并将Object转型为E
}


5.  ArrayList的remove操作

//第一种是根据value值来删除,会从前向后进行遍历,删除第一个出现的元素
public boolean remove(Object o) {
if (o == null) {//移除对象数组elementData中的第一个null
for (int index = 0; index < size; index++){
if (elementData[index] == null) {
fastRemove(index);
return true;
}
}
}
else {//移除对象数组elementData中的第一个
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
} private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)//删除的不是最后一个元素
System.arraycopy(elementData, index + 1, elementData, index,numMoved);//删除的元素到最后的元素整块前移
//要删除的是最后一个元素的情况,将最后一个元素设为null
elementData[--size] = null;
}
//第二种是根据下标值来删除某个子元素
public E remove(int index) {
RangeCheck(index);//检查索引范围
E oldValue = (E) elementData[index];//被删除的元素
fastRemove(index);
return oldValue;
}

6.  ArrayList的contains操作

//判断动态数组是否包含元素o,是一个很常用的功能
//内部调用的是indexOf()方法,只返回遍历到的第一个满足条件的值
//在遍历时是区分null值和非null值的,这也说明ArrayList是支持null值的。 public boolean contains(Object o) {
return indexOf(o) >= 0;
}
/**
* 返回第一个出现的元素o的索引位置
*/
public int indexOf(Object o) {
if (o == null) {//返回第一个null的索引
for (int i = 0; i < size; i++)
if (elementData[i] == null)
return i;
} else {//返回第一个o的索引
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;//若不包含,返回-1
}
//说到indexOf()就不得不提及lastIndexOf()方法
//用于寻找最后一个出现的元素o的索引位置,原理就是反向对数组进行遍历。
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size - 1; i >= 0; i--)
if (elementData[i] == null)
return i;
} else {
for (int i = size - 1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

7.  ArrayList的遍历操作

//1. 通过迭代器即通过Itreator去遍历
Integer value=null;
Iterator i=list.iterator();
while(i.hasNext()){
value=(Interger)i.next();
} //2. 随机访问模式,通过索引值去遍历所有元素,这种方式是速度最快的
Interger value=null;
int size=list.size();
fo(int i=0;i<size;i++){
value=(Integer)list.get(i);
}
//3. ForEach循环遍历
//ForEach 编译成字节码之后,本质上还是使用迭代器实现的
//只比迭代器遍历多了生成中间变量这一步,因为性能也略微下降了一些,因此这种方法是速度最慢的
Integer value=null;
for(Integer i : list){
value=i;
}

8.  ArrayList和Vector的关系

Vector与ArrayList基本是一致的,也是基于数组实现的,都有扩容机制,都允许元素为null。

但是不同点也是很明显的,这也是面试容易被问到的地方:

(1)Vector是线程安全的,会在可能出现线程安全的方法前面加上synchronized关键字。

(2)构造方法上面, Vector比 ArrayList多了一个方法,这个方法能够使Vector指定初始化容量(默认情况下,Vector容量为10)。

public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}

(3)两者的扩容方案不同。

//Vector的扩容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
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);
}

在 ArrayList中,扩容的时候一般都是增加0.5倍左右,而在 Vector中,如果在构造方法中指定了capacityIncrement的值,就会在原来容量的基础上线性增加 capacityIncrement个,如果没有指定,则默认增加一倍容量。如果设置后的新容量还不够,则直接设置新容量为传入的参数(所需的容量),而后同样用Arrays.copyof()方法将元素拷贝到新的数组。

(4)Vector已经被废弃掉,以下是官方给出的解释:

//If you need synchronization, a Vector will be slightly faster than an ArrayList synchronized with *Collections.synchronizedList.
// But Vector has loads of legacy operations,
//so be careful to always manipulate the Vector *with the List interface
//or else you will not be able to replace the implementation at a later time. //如果你需要做同步,Vector会比调用Collections.synchronizedList方法的ArrayList更快一点
//但是Vector有很多遗留下来的操作
//所以用List接口操作Vector时要小心,否则之后就不能改变接口的实现了
//说白了就是List list = new Vector()的时候要小心一点

至此关于ArrayList和Vector的区别也总结完毕了。

转载请注明出处:SEU_Calvin的博客。请多点下面的赞支持~ 谢谢


Java集合——ArrayList源码详解的更多相关文章

  1. Java集合——TreeMap源码详解

    )TreeMap 是一个有序的key-value集合,它是通过红黑树实现的.因为红黑树是平衡的二叉搜索树,所以其put(包含update操作).get.remove的时间复杂度都为log(n). (2 ...

  2. Java集合——LinkedList源码详解

    )LinkedList直接继承于AbstractSequentialList,同时实现了List接口,也实现了Deque接口. AbstractSequentialList为顺序访问的数据存储结构提供 ...

  3. Java集合——LinkedHashMap源码详解

    个KV.LinkedHashMap不仅像HashMap那样对其进行基于哈希表和单链表的Entry数组+ next链表的存储方式,而且还结合了LinkedList的优点,为每个Entry节点增加了前驱和 ...

  4. 数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解

    数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...

  5. Java集合ArrayList源码解读

    最近在回顾数据结构,想到JDK这样好的代码资源不利用有点可惜,这是第一篇,花了心思.篇幅有点长,希望想看的朋友认真看下去,提出宝贵的意见.  :) 内部原理 ArrayList 的3个字段 priva ...

  6. Java集合-ArrayList源码解析-JDK1.8

    ◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...

  7. Java集合-ArrayList源码分析

    目录 1.结构特性 2.构造函数 3.成员变量 4.常用的成员方法 5.底层数组扩容原理 6.序列化原理 7.集合元素排序 8.迭代器的实现 9.总结 1.结构特性 Java ArrayList类使用 ...

  8. java 基础数据结构源码详解及数据结构算法

    http://www.cnblogs.com/skywang12345/category/455711.html http://www.cnblogs.com/liqiu/p/3302607.html

  9. 【java集合框架源码剖析系列】java源码剖析之ArrayList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 本博客将从源码角度带领大家学习关于ArrayList的知识. 一ArrayList类的定义: public class Arr ...

随机推荐

  1. webpack之react开发前准备

    今天抽出空来,翻了翻webpack之react的书籍,看到刚出的es6语法,貌似是简单了不少,但是兼容性确实不容乐观,如果实在要用那也不是不可以的,首先就跟随我来看下这个插件吧: Babel:这个插件 ...

  2. ECharts属性设置

    theme = { // 全图默认背景 // backgroundColor: ‘rgba(0,0,0,0)’, // 默认色板 color: ['#ff7f50','#87cefa','#da70d ...

  3. (一)自定义ViewGroup绘制出菜单

    从网上学习了hyman大神的卫星菜单实现,自己特意亲自又写了一编代码,对自定义ViewGroup的理解又深入了一点.我坚信只有自己写出来的知识才会有更加好的的掌握.因此也在自己的博客中将这个卫星菜单的 ...

  4. django+ajax用FileResponse文件下载到浏览器过程中遇到的问题

    问题: 公司的需求是从mongodb中查找数据并下载回本地,但是在将文件从mongodb通过django服务端,然后从django服务端向浏览器下载文件.但是在下载的时候出了些问题.由于是用的ajax ...

  5. python-二维数组实现90度旋转

    本篇主要介绍了对一个N*N的数组,如果进行90度的旋转 首先,定义一个一维数组很简单,如下: a = [i for i in range(10)] print(a) -----结果----- 0, 1 ...

  6. linux nginx 配置php

    linux nginx 配置php   下载php源码 解压 configure ./configure --prefix=/usr/local/php --enable-fpm --with-mcr ...

  7. 前端面试题(copy)

    前端开发面试知识点大纲: HTML&CSS: 对Web标准的理解.浏览器内核差异.兼容性.hack.CSS基本功:布局.盒子模型.选择器优先级及使用.HTML5.CSS3.移动端适应. Jav ...

  8. UIScrollView的常用属性

    UIScrollView的常用属性

  9. Swift:一个基于.NET Core的分布式批处理框架

    Swift是什么 从文章的标题可知:此Swift非Apple那个Swift,只是考虑这个词的含义比较适合. Swift是一个基于.NET Core的分布式批处理框架,支持将作业分割后分发到多台服务器并 ...

  10. Flask—10-项目部署(02)

    项目部署 WEB工作原理 客户端(chrom) <=> WEB服务器(nginx) <=> WSGI(uWSGI) <=> Python(Flask) <=& ...