文章目录
要点速记: 扩容=1.5倍+1,数组和链表的区别
1. 类结构
public class ArrayList extends AbstractList implements List , RandomAccess, Cloneable, java.io.Serializable
1.1、 实现接口
- RandmoAccess接口:
是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问
想详细了解的呢可以看RandomAccess接口的使用
- Cloneable接口:
即覆盖了函数clone(),能被克隆 - Serializable接口
意味着ArrayList支持序列化,能通过序列化去传输 - List接口:
在Java集合分类中,List代表有序,可重复
具体和下面父类一起说 - 父类AbstractList
ArrayList 继承了AbstractList,父类也实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能
1.2、 它跟数组的关系?
1.2.1、数组VS链表
Array(数组),数据结构适合内存结构也是连续的(适合遍历),随机访问(适合查找),不适合增删(链表则相反),同时在创建数组的同时你必须指定大小
想知道详细的可以看看数组与链表的优缺点
1.3.2、 数组 VS ArrayList
- Array类型的变量在声明的同时必须进行实例化(至少得初始化数组的大小),而ArrayList可以只是先声明。
- Array只能存储同构的对象,而ArrayList可以存储异构的对象。
List内部就是使用"object[] _items;"这样一个私有字段来封装对象的) - 在CLR托管对中的存放方式:
Array是始终是连续存放的,而ArrayList的存放不一定连续。 - 初始化大小:
Array对象的初始化必须只定指定大小,且创建后的数组大小是固定的,
而ArrayList的大小可以动态指定,其大小可以在初始化时指定,也可以不指定,也就是说该对象的空间可以任意增加。 - Array不能够随意添加和删除其中的项,而ArrayList可以在任意位置插入和删除项。
同构和异构:
同构的对象是指类型相同的对象,若声明为int[]的数组就只能存放整形数据,string[]只能存放字符型数据,但声明为object[]的数组除外。
而ArrayList可以存放任何不同类型的数据(因为它里面存放的都是被装箱了的Object型对象,实际上Array
回到正文了,去看看ArrayList源码罗
2、探索源码
2.1、 构造
// ArrayList带容量大小的构造函数。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
// 新建一个数组
this.elementData = new Object[initialCapacity];
}
// ArrayList构造函数。默认容量是10。
public ArrayList() {
this(10);
}
// 构造一个包含指定元素的list,这些元素的是按照Collection的迭代器返回的顺序排列的
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
其中构造方法牵扯到了两个属性
// 保存ArrayList中数据的数组
private transient Object[] elementData;
// ArrayList中实际数据的数量
private int size;
(1) elementData :Object[]类型的数组,它保存了添加到ArrayList中的元素。
实际上,elementData是个动态数组,我们能通过构造函数 ArrayList(int initialCapacity)来执行它的初始容量为initialCapacity;
如果通过不含参数的构造函数ArrayList()来创建ArrayList,则elementData的容量默认是10。elementData数组的大小会根据ArrayList容量的增长而动态的增长
具体的增长方式,请参考源码分析中的ensureCapacity()函数。
(2) size 则是动态数组的实际大小。
2.2、添加
/**
* 添加一个元素
*/
public boolean add(E e) {
// 进行扩容检查
ensureCapacity( size + 1); // Increments modCount
// 将e增加至list的数据尾部,容量+1
elementData[size ++] = e;
return true;
}
/**
* 在指定位置添加一个元素
*/
public void add(int index, E element) {
// 判断索引是否越界,这里会抛出多么熟悉的异常。。。
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: " +size);
// 进行扩容检查
ensureCapacity( size+1); // Increments modCount
// 对数组进行复制处理,目的就是空出index的位置插入element,并将index后的元素位移一个位置
System. arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将指定的index位置赋值为element
elementData[index] = element;
// list容量+1
size++;
}
/**
* 增加一个集合元素
*/
public boolean addAll(Collection<? extends E> c) {
//将c转换为数组
Object[] a = c.toArray();
int numNew = a.length ;
//扩容检查
ensureCapacity( size + numNew); // Increments modCount
//将c添加至list的数据尾部
System. arraycopy(a, 0, elementData, size, numNew);
//更新当前容器大小
size += numNew;
return numNew != 0;
}
/**
* 在指定位置,增加一个集合元素
*/
public boolean addAll(int index, Collection<? extends E> c) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: " + index + ", Size: " + size);
Object[] a = c.toArray();
int numNew = a.length ;
ensureCapacity( size + numNew); // Increments modCount
// 计算需要移动的长度(index之后的元素个数)
int numMoved = size - index;
// 数组复制,空出第index到index+numNum的位置,即将数组index后的元素向右移动numNum个位置
if (numMoved > 0)
System. arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 将要插入的集合元素复制到数组空出的位置中
System. arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
/**
* 数组容量检查,不够时则进行扩容
*/
public void ensureCapacity( int minCapacity) {
modCount++;
// 当前数组的长度
int oldCapacity = elementData .length;
// 最小需要的容量大于当前数组的长度则进行扩容
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
// 新扩容的数组长度为旧容量的1.5倍+1
int newCapacity = (oldCapacity * 3)/2 + 1;
// 如果新扩容的数组长度还是比最小需要的容量小,则以最小需要的容量为长度进行扩容
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
// 进行数据拷贝,Arrays.copyOf底层实现是System.arrayCopy()
elementData = Arrays.copyOf( elementData, newCapacity);
}
}
2.3、 删除
/**
* 根据索引位置删除元素
*/
public E remove( int index) {
// 数组越界检查
RangeCheck(index);
modCount++;
// 取出要删除位置的元素,供返回使用
E oldValue = (E) elementData[index];
// 计算数组要复制的数量
int numMoved = size - index - 1;
// 数组复制,就是将index之后的元素往前移动一个位置
if (numMoved > 0)
System. arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将数组最后一个元素置空(因为删除了一个元素,然后index后面的元素都向前移动了,所以最后一个就没用了),好让gc尽快回收
// 不要忘了size减一
elementData[--size ] = null; // Let gc do its work
return oldValue;
}
/**
* 根据元素内容删除,只删除匹配的第一个
*/
public boolean remove(Object o) {
// 对要删除的元素进行null判断
// 对数据元素进行遍历查找,知道找到第一个要删除的元素,删除后进行返回,如果要删除的元素正好是最后一个那就惨了,时间复杂度可达O(n) 。。。
if (o == null) {
for (int index = 0; index < size; index++)
// null值要用==比较
if (elementData [index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
// 非null当然是用equals比较了
if (o.equals(elementData [index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
// 原理和之前的add一样,还是进行数组复制,将index后的元素向前移动一个位置,不细解释了,
int numMoved = size - index - 1;
if (numMoved > 0)
System. arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size ] = null; // Let gc do its work
}
/**
* 数组越界检查
*/
private void RangeCheck(int index) {
if (index >= size )
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: " +size);
}
2.4 更新
/**
* 将指定位置的元素更新为新元素
*/
public E set( int index, E element) {
// 数组越界检查
RangeCheck(index);
// 取出要更新位置的元素,供返回使用
E oldValue = (E) elementData[index];
// 将该位置赋值为行的元素
elementData[index] = element;
// 返回旧元素
return oldValue;
}
2.5查找
/**
* 查找指定位置上的元素
*/
public E get( int index) {
RangeCheck(index);
return (E) elementData [index];
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData [i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData [i]))
return i;
}
return -1;
}
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;
}
contains主要是检查indexOf,也就是元素在list中出现的索引位置也就是数组下标,再看indexOf和lastIndexOf代码是不是很熟悉
没错,和public boolean remove(Object o) 的代码一样,都是元素null判断,都是循环比较,不多说了。。。但是要知道,最差的情况(要找的元素是最后一个)也是很惨的。。。
3. ArrayList3种遍历方式
(01) 第一种,通过迭代器遍历。即通过Iterator去遍历。
Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}
(02) 第二种,随机访问,通过索引值去遍历。
由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。
Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
value = (Integer)list.get(i);
}
(03) 第三种,for循环遍历。如下:
Integer value = null;
for (Integer integ:list) {
value = integ;
}
总结迭代速度是最慢的,随机访问是最快得
4 总结
ArrayList和LinkedList的区别
- 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.