ArrayList的本质还是数组,是Java一种可变长数组的数据结构,概括的说底层实现机制,是扩容和拷贝元素。ArrayList实现了List等好多个接口 ,主要看一下List接口。
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable
List接口里面提供了一系列的基本方法,都是对一个存储结构的基本方法,增删改查方法。罗列一些
int size();
boolean isEmpty();
boolean contains(Object o);
boolean add(E e);
E remove(int index);
void clear();
ArrayList构造方法有带参数和不带参数构造,带参构造可以指定数组长度
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
无参构造的时候默认数组长度为10。
public ArrayList() {
this(10); //这里调用带参数构造方法
}
除此之外带参数构造的还有传递一个集合类的元素构造,比如构造一个ArrayList的时候可以传递一个ArrayList
public static void main(String[] args) {
ArrayList ar_list = new ArrayList();
ar_list.add(2);
ar_list.add(4);
ar_list.add(5);
ar_list.add(6);
ArrayList ar_list2= new ArrayList(ar_list);
System.out.println(ar_list);
System.out.println(ar_list2);
}
public ArrayList(Collection c) {
elementData = c.toArray(); //将传递进来的集合类转为数组 赋值给arraylist里面的数组
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class) //这里的if判断的所用在于 如果传递进来的集合类 转为数组 类型可能部位object 进行进一步的复制赋值
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
存放在ArrayList元素放在一个Object类型的数组里面,这个数组被transient关键字修饰,防止修饰的东西不被序列化,ArrayList是实现了Serializable接口的,一个对象只要实现了Serilizable接口,这个对象就可以被序列化,一个类有的属性不需要被序列化,就加上transient关键词。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*add元素时候真正存放数据的地方/
private transient Object[] elementData;
1、trimToSize()方法
arrayList里面不是有size。size代表的数组的存放元素个数,并不是真正数组的长度,数组长度是elementData.length,这个方法的作用在于把arrayList里面的数组容量修剪为列表的当前大小,但是元素还是保留不变的,可能数组长度为10但是里面只有4个元素,通过这个方法可以修剪这个数组,变为长度为4,释放掉了数组中空闲的地方。
public void trimToSize() {
modCount++; //这是从继承那里来的元素记录操作次数
int oldCapacity = elementData.length;
if (size < oldCapacity) {
elementData = Arrays.copyOf(elementData, size); //将数组的大小改为当前数组存放元素个数
}
}
2、ensureCapacity()扩容的方法
这个方法是在往数组添加元素之前调用,判断当前的数组是否还有空余空间存放要添加的元素,
public void ensureCapacity(int minCapacity) {
modCount++; //这里也有
int oldCapacity = elementData.length; //得到当前数组的长度 也就是最大容量
if (minCapacity > oldCapacity) { //传递进来的minCapacity是添加一个元素之后的size 也就是size+1,判断加一个元素之后是否超过了数组长度
Object oldData[] = elementData; //保存一下原来数组所有的元素 好像没啥用
int newCapacity = (oldCapacity * 3)/2 + 1; //对于新的数组长度是原来长度的1.5倍 (oldCapacity * 3)/2 可能会向下取整 比如 3 * 3 / 2 =4 所以后面再加一个
if (newCapacity < minCapacity) //放大之后的长度还小于mincapactity 的话 就是这个oldcapacity放大之后超过了int上限了 成了负数了
newCapacity = minCapacity; //那就不放大了 保持原有长度 已是最大的长度了
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity); //拷贝元素 再copyof方法里面会对elementdata扩容 长度就是newCapacity
}
}
3、add()方法
在添加一个元素之前会调用ensureCapacity方法判断剩余数组空间够不够,需不需要扩容,要保证新添加的元素要有地方存放。
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return true (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacity(size + 1); // 传递进去的就是size + 1也就是oldcapacity
elementData[size++] = e;
return true;
}
add( )方法还有带双参数的添加元素,在指定index的位置插入元素,看这个源码之前先看一下arraycopy方法,这是System类下一个静态方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
src:源数组; srcPos:源数组要复制的起始位置;
dest:目的数组; destPos:目的数组放置的起始位置; length:复制的长度。
在指定位置插入元素,这个index一定是在0-size之间插入的,否则也会报下标越界异常。
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1); // 检查是够需要扩容
System.arraycopy(elementData, index, elementData, index + 1,
size - index); //完成拷贝数组 实际上给是index后面的元素后移一位 空出来index的位置
elementData[index] = element; //index位置赋值
size++;
}
4.indexOf()和contains()方法
主要看一下indexOf( )方法,contains( )方法也是通过调用indexOf( )方法实现的。
再数组里面找!找到第一出现指定元素o的位置,返回下标,没有的话就返回-1。不难理解。还有和indexOf方法类似lastIndexOf方法,区别就是返回最后一次指定元素出现的下标,就是从数组后面开始遍历查找。
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;
}
而contains( )方法就是单纯查看arrayList也就是数组elementdata里面有没有一个指定的元素,直接调用indexOf方法,返回是一个下标,下标小于0的话也就是-1说明就是不存在该元素的。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
5、RangeCheck( )方法
在调用get()方法之前调用RangeCheck( )检查你传递进来的index是否超过了size,超过了size会抛出一个下标越界的异常。
严格的说并没有下标越界,超过了数组length才算。但是去访问超过了size的内容也不合适。
public E get(int index) {
RangeCheck(index);
return (E) elementData[index]; //得到指定下标index处的元素
}
private void RangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
}
6、remove()方法
remove()方法有重载,有移除指定下标的元素,有移除指定的元素,看完源码后,移除类的方法都是通过数组拷贝完成的arraycopy方法来完成的。
看一个最简单的移除指定位置元素的方法fastRemove()。
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1; //计算index后面还有几个元素 多算了一个 后面--size会用上
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved); //从index+1位置开始拷贝到index位置 拷贝元素个数 上面计算好了
elementData[--size] = null; // Let gc do its work 让gc做了回收的事情
}
移除指定的元素,移除的是第一次出现的元素
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)//找到这个元素出现的位置index 为啥不直接用indexOf函数
if (elementData[index] == null) {
fastRemove(index); //直接移除掉
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false; //说明不存在要移除的元素
}
这个移除元素的方法和fastRemove( )相似,不过这个移除完了之后返回了移除元素,而astRemove( )移除是没有返回值,并且是封装的。
public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];//保存一下index处要移除的元素
int numMoved = size - index - 1;//和fastRemove方法一样 计算index后面的元素个数
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
移除指定一段的元素,实际移除元素之后是一个前闭后开的区间[fromIndex, toIndex)
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;//移除的区间[fromIndex, toIndex]是在0-size里面的
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);//还是通过拷贝数组完成的移除
// Let gc do its work
int newSize = size - (toIndex-fromIndex);//计算新的数组元素个数
while (size != newSize)
elementData[--size] = null; //把toIndex后面到size那里元素赋值为null 交给gc释放
}
最终的结果就是{8,9,30,16,12}。
7、clear()方法
清空arrayList的存储部分,size为0 其实也可以直接size为0,下次添加元素的时候覆盖掉,但是这并没有在内存的角度上清空
public void clear() {
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;//把数组里面的每个元素赋值为null 每份空间都是空闲的了
size = 0;//size重置为0 这又是一个新的arrayList了
}
总的来说,arrayList的本质就是通过扩容和拷贝数组完成的这些功能,比起链表要轻便的多,但是也付出了空间的代价,多申请一部分空间,各有优缺点,对于双向链表,插入和删除操作是数组所不能比的。