JDK源码学习之ArrayList

一、概述

      ArrayList是List接口的一个实现类,是非线程安全的。

二、类头

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

    ArrayList类是个泛型类,继承自AbstractList泛型类,实现了list、RandomAccess、Cloneable跟Serializable接口。

   其中RandomAccess接口是一个标记接口,表示该类支持快速随机访问,ArrayList实现该接口说明,通过

for (int i=0, i<list.size(); i++)list.get(i);的运行速度要快于以下循环:

for (Iterator i=list.iterator(); i.hasNext(); ) i.next();(本机测试相差两倍左右,List要足够大
  Cloneable也是一个标记接口,表示该类可以被拷贝。最后的Serializable接口表示可以被序列化。 

三、属性

  private static final long serialVersionUID = 8683452581122892189L;//可序列化编号
  private transient Object[] elementData;//队列是通过数组来实现的,所以查找快速更新快,添加删除慢,队列中存放的数据(引用)这里使用了transient 关键字,作用是防止被序列化,这里有个问题,声明为transient为什么还能序列化成功呢?答案很简单ArrayList重写了writeObject方法,如果声明该方法,它将会被ObjectOutputStream调用而不是默认的序列化进程。它们既不存在于java.lang.Object,也没有在Serializable中声明。而是在ObjectOutputStream中,如何使用它们的呢?ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为private以至于供ObjectOutputStream来使用。为什么要这么做呢?比较靠谱的原因是:“ArrayList是会开辟多余空间来保存数据的,而系列化和反序列化这些没有存放数据的空间是要消耗更多资源的,所以ArrayList的数组就声明为transient,告诉虚拟机这个你别管,我自己来处理,然后就自己实现write/readObject方法,仅仅系列化已经存放的数据。” 

  private int size;//队列大小,标示队列的存放数据的大小,小于等于elementData.length

四.内部常用方法

  三种构造器:

  public ArrayList(int initialCapacity)指定大小

  public ArrayList() 不指定大小,默认为10

  public ArrayList(Collection<? extends E> c)通过已知的集合来初始化

  常用的方法:

   1、乱七八糟方法  

    

public void trimToSize() {//方法作用类似String.trim方法,去掉无用队列中没有使用的数据modCount++;//父类提供的属性值默认为0,用来记录对队列的修改次数,作用是判断是否存在并发修改,

进而抛出ConcurrentModificationException,从而保证Iterator遍历或者反序列化的时候不会因改变队列而产生少值问题,避免潜在bug。
	int oldCapacity = elementData.length;
	if (size < oldCapacity) {
            elementData = Arrays.copyOf(elementData, size);
	}
 }
public void ensureCapacity(int minCapacity)//对以后队列进行扩容,如果原数组大小小于该值,对已有数组进行扩容一次,大小为原来的1.5倍,不够为minCapacity大小
public boolean contains(Object o) {//队列是否包含该对象,传人null也有可能找到
    return indexOf(o) >= 0;//
}
public int indexOf(Object o) {//返回该对象所在位置,找到返回0-size-1数字,没有返回-1
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]))//调用的是equals方法,默认比较的是引用,使用的时候要注意是否是想要比较引用来判断对象相同,否则容易出现问题。
   return i;
}
return -1;
}
public Object[] toArray() {//转化成数组
     return Arrays.copyOf(elementData, size);
}

public void clear() {//清空队列
	modCount++;
	for (int i = 0; i < size; i++)
	    elementData[i] = null; // 引用清空,便于垃圾回收
            size = 0;//长度变为0
  }
  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();
        }
  }
public Object clone()//注意ArrayList提供的克隆方法是影子克隆即浅克隆,引用值还是指向原对象,使用时要小心

  2、查找方法

public E get(int index) {//得到指定位置的数据
	RangeCheck(index);//之前先做范围验证,防止返回elementData中的无效数据,
	return (E) elementData[index];//数组实现查找快
}

 3、 添加方法

public boolean add(E e) {//添加一条记录
	ensureCapacity(size + 1);  // 首先扩容,保证有数组有足够的空间来容纳新数据,这里存在效率问题,可以预估计大小的数据直接声明创建好,否则容易重复多次拷贝
	elementData[size++] = e;//没有判断是否为null,所以ArrayList可以存放null值
	return true;
 }
public boolean addAll(Collection<? extends E> c) {//添加一个集合,大数据的时候比单个添加效率高
	Object[] a = c.toArray();
        int numNew = a.length;
	ensureCapacity(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);//调用本地方法,快速复制,自己进行数组赋值的时候可以直接采用这个方法效率高。可以自己拷贝自己没有问题
        size += numNew;
	return numNew != 0;
    }

 4、 删除提供了两种方法:通过通过索引删除和引用删除实现如下

public E remove(int index) {//删除指定索引元素,从0开始
	RangeCheck(index);//范围正确性验证
        modCount++;
	E oldValue = (E) elementData[index];
	int numMoved = size - index - 1;
	if (numMoved > 0)//判断是否是删除最后节点
	    System.arraycopy(elementData, index+1, elementData, index,
			     numMoved);//数组赋值,把index+1之后的数据赋值到从index开始的长度是numMoved的数组里面去,相当于把删除节点之后的数据整体后移一位,所以直接不适用迭代器

遍历的时候,如果直接删除,会丢数据甚至有可能产生越界异常。
	elementData[--size] = null; // 引用清空,便于垃圾回收
	return oldValue;//返回被删除节点值
    }
public boolean remove(Object o) {//通过引用删除
	if (o == null) {//传入null
            for (int index = 0; index < size; index++)
		if (elementData[index] == null) {
		    fastRemove(index);//快速删除就是没有做范围判断,直接删除,内部私有方法,不对外提供,API提供的都是安全的方法,有异常也会抛出,
		    return true;
		}
	} else {
	    for (int index = 0; index < size; index++)
		if (o.equals(elementData[index])) {//通过equals判断
		    fastRemove(index);
		    return true;
		}
        }
	return false;
    }

五、总结

        通过阅读源码,知道 ArrayList相当于动态数组 ,所以查找特别快,但是修改删除比较耗时,需要进行数组扩容 ,最好开始使用的时候创建合适大小的数组,以后超范围了ArrayList会自己进行扩充,不用担心越界问题。
        ArrayList允许添加null对象,使用contains方法时要注意会调用equals进行判断,默认比较的是引用。
        对ArrayList进行clone的时候要注意,是浅拷贝。
        学习modCount的使用,数组复制的时候多使用System.arrayCopy()方法速度快。






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值