java ArrayList 源代码分析

首先看一下ArrayList的实现:继承自AbstractList, 实现了List,RandomAccess, Cloneable,java.io.Serializable接口,
1. 可以看出它是支持泛型的,因此可以存储不同类型的对象,
2. 实现了RandomAccess, Cloneable,java.io.Serializable接口,但是其实他们都是空接口,里面没有任何方法声明,只不过实现了这几个接口就意味着,它支持快速随机访问,支持浅拷贝,支持序列化与反序列化

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

第二: 它的底层实现,它的底层其实就是数组实现的,而且里面存储的也是对象的地址,并不是对象本身, 代码如下,其实是对象数组,transient 的含义是去序列化的意思,即在这个类序列化后保存到磁盘或者输出到输出流的时候,这个对象数组是不被保存或者输出的,原因如下: ArrayList的容量一般都是预留一些容量,等到容量不够时再拓展,那么就会出现容量还有冗余的情况,如果这时候进行序列化,整个数组都会被序列化,连后面没有意义空元素的也被序列化。这些是不应该被存储的,所以java的设计者,就为这个类提供了一个writeObject方法,实现了Serializable接口,显式的为每个实际的数组元素进行序列化,只序列化有用的元素。

private transient Object[] elementData;
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();
    }

    }

第三: ArrayList的构造方法:

// 带参数的构造方法,根据开发者定义的大小创建数组
public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
//  JDK 1.6中的默认构造函数,默认的大小为10
    public ArrayList() {
        super();
        this(10);
    }
    // jDK 1.7中的默认构造函数,会先在内存中分配一个对象的内存空间,但是这个对象是没有长度的。但是在你进行添加的时候,默认的会去拿对象的默认大小 10 来作比较,然后获得一个长度为10的数组
    public ArrayList() {
        super();
        // EMPTY_ELEMENTDATA 是空的
        this.elementData = EMPTY_ELEMENTDATA;
    }
//  将整个数组复制到新的数组中
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

第四: add() 方法,首先检查是否超出了当前数组的容量, 然后再添加,如果超出了容量, 那么 ArrayList 底层会新生成一个数组,长度为原数组的 1.5 倍,然后将原数组的内容复制到新数组当中,并且后续增加的内容都会放到新数组当中。当新数组无法容纳增加的元素时,重复该
过程。
复制函数为elementData=Arrays.copyOf(elementData, newCapacity), 含义是将旧的数组elementData进行拷贝,返回一个拷贝的新的数组,其长度大小为newCapacity,旧的数组的内容也会被全部拷贝到这个新的数组中去。

(这个动态扩容的过程是核心知识点)
特别注意的是:容量拓展,是创建一个新的数组,然后将旧数组上的数组copy到新数组,这是一个很大的消耗,所以在我们使用ArrayList时,最好能预计数据的大小,在第一次创建时就申请够内存。

JDK 1.6 与JDK 1.7的区别:
第一:在容量进行扩展的时候,jdk 1.6 整除运算将容量扩展为原来的1.5倍加1,而jdk1.7是利用位运算,从效率上,jdk1.7就要快于jdk1.6。

第二:在算出newCapacity时,其没有和ArrayList所定义的MAX_ARRAY_SIZE作比较,为什么没有进行比较呢,原因是jdk1.6没有定义这个MAX_ARRAY_SIZE最大容量,也就是说,其没有最大容量限制的,但是jdk1.7做了一个改进,进行了容量限制。

// 添加一个对象
    public boolean add(E e) {
       //检查是否超出了当前数组的容量,如果超出,则进行扩容
       ensureCapacityInternal(size + 1);  // Increments modCount!!
       elementData[size++] = e;
       return true;
    }
//检查容量,并且扩容的几个函数  ensureCapacityInternal 
private void ensureCapacityInternal(int minCapacity) {
        modCount++; // 继承自父类 AbstractList
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // >> 右移位运算符,相当于oldCapacity /2 ,但是位运算符的速度要高于除法的速度,
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 检查是否超过了ArrayList定义的是最大容量,如果minCapacity大于最大容量,则新容量则为ArrayList定义的最大容量,否则,新容量大小则为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);
    }

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }





    // 添加一个对象到指定index位置
        public void add(int index, E element) {
        //进行越界检查
       rangeCheckForAdd(index);
       //容量检查
       ensureCapacityInternal(size + 1);  // Increments modCount!!
       // 将从index开始的元素到最后的元素,从index+1位置开始往后复制,然后将指定元素插入到index位置,然后将容量size增加1,几个参数的意义,arraycopy(Object src,  // 目标对象
             int srcPos,  // 目标对象开始拷贝的位置
             Object dest, // 拷贝到的新的对象
             int destPos, // 新的对象开始拷贝的位置
             int length)  // 目标对象需要拷贝的长度
       System.arraycopy(elementData, index, elementData, index + 1, size - index);
       elementData[index] = element;
       size++;
    }
// 添加一个集合
 public boolean addAll(Collection<? extends E> c) {
       Object[] a = c.toArray();
       int numNew = a.length;
       // 容量检查
       ensureCapacityInternal(size + numNew);  // Increments modCount
       // 复制
       System.arraycopy(a, 0, elementData, size, numNew);
       size += numNew;
       return numNew != 0;
    }

第五: get方法:

    public E get(int index) {
        // 越界检查
        rangeCheck(index);
        return elementData(index);
        }
    private void rangeCheck(int index) {
        if (index >= size)
            throw new     IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

第六: remove() 删除对象,最后是会返回删除元素的值的

// 根据索引删除
 public E remove(int index) {
       // 越界检查
       rangeCheck(index);

       modCount++;
       E oldValue = elementData(index);
       // 要移动的数据个数
       int numMoved = size - index - 1;
       if (numMoved > 0)
          //将后面的数据向前移动,通过arraycopy方法
          System.arraycopy(elementData, index+1, elementData, index, numMoved);
       elementData[--size] = null; // Let gc do its work

       return oldValue;
    } 
   // 根据对象删除元素,返回值为boolean 型
   public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                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;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
     // 这里我们可以发现有一个fastRenove,其实删除的方法是一致的,但是为什么还要写个fastRemove(),看代码就知道,fastRemove 跳过了index越界处理,并且不用返回要删除的元素值。
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        elementData[--size] = null; // Let gc do its work
    }

第七: clear() clear方法并不是把整个数组都删除,因为毕竟已经申请了内存,这样删了,很可惜,因为可能以后还用得着,这就免去了再次去申请内存的麻烦。这里的clear只是把每个元素的都置为null,并把size设为 0.

    public void clear() {
    modCount++;

    // Let gc do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
    }

第八: set(int index,E element) 这个很简单

    public E set(int index, E element) {
       rangeCheck(index);

       E oldValue = elementData(index);
       elementData[index] = element;
       return oldValue;
    }

第九: toArray(), 将集合中的元素以数组形式返回,返回的仍旧是对象数组 Object[]

    // 返回的仍旧是对象数组  Object[]    
    public Object[] toArray() {
       return Arrays.copyOf(elementData, size);
    }
    // 直接返回一个T类型的数组,可以使A类,但是要求数组中存放的都是A类型的对象
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents: 这个函数要求  Arrays.copyOf, 数据中的类型要与要返回的新类型T保持一致,否则便会抛出异常
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

这里可以讲解一下 getClass() 函数,它是属于每个对象Object 都拥有的函数,它属于反射学习, 可以理解为在运行时期获取对象类型信息的操作。Object类中包含一个方法名叫getClass,利用这个方法就可以获得一个实例的类型类,例如:

A a = new A();
if(a.getClass()==A.class)
System.out.println("equal");
else System.out.println("unequal");

在获得类型类之后,你就可以调用其中的一些方法获得类型的信息了,主要的方法有:

getName():String:获得该类型的全称名称。
getSuperClass():Class:获得该类型的直接父类,如果该类型没有直接父类,那么返回null。
getInterfaces():Class[]:获得该类型实现的所有接口。
isArray():boolean:判断该类型是否是数组。
isEnum():boolean:判断该类型是否是枚举类型。
isInterface():boolean:判断该类型是否是接口。

第十: isEmpty(), 判断size是否为0,,为0则返回true。

    public boolean isEmpty() {
       return size == 0;
    }

第十一: indexOf(Object o) 根据传入的Object,返回其在数组中的下班位置,若不存在则返回-1

    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(Object o) 调用indexOf()方法返回的位置与0比较,若大于等于0,则证明这个元素是存在,若不存在indeOf()会返回-1,则返回true。

    public boolean contains(Object o) {
       return indexOf(o) >= 0;
    }

第十三: clone() ArrayList实现了Cloneable接口,支持返回浅拷贝,这里是重写了Object类的clone方法,返回的是一个副本(对象实例不同,内容相同,因为对象实例都不同,所以modCount也设为0)

public Object clone() {
        try {
            @SuppressWarnings("unchecked")
                ArrayList<E> v = (ArrayList<E>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError();
        }
    }

最后,我看完源码还有一个疑问,那个modCount 是干什么用的?modCount 继承自抽象类AbstractList,在ArrayList, LinkedList, HashMap 等等的内部实现增,删,改中我们总能看到modCount的身影,modCount字面意思就是修改次数,但为什么要记录modCount的修改次数呢?
大家发现一个公共特点没有,所有使用modCount属性的全是线程不安全的,这是为什么呢?说明这个玩意肯定和线程安全有关系喽,那有什么关系呢

阅读源码,发现这玩意只有在本数据结构对应迭代器中才使用,以HashMap为例:


private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry<K,V> current; // current entry

HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}

public final boolean hasNext() {
return next != null;
}

final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();

if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}

public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}

由以上代码可以看出,在一个迭代器初始的时候会赋予它调用这个迭代器的对象的modCount,如何在迭代器遍历的过程中,一旦发现这个对象的modCount和迭代器中存储的expectedModCount不一样那就抛异常
好的,下面是这个的完整解释
Fail-Fast 机制 快速失败机制
我们知道 java.util.HashMap 不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。这一策略在源码中的实现是通过 modCount 域,modCount 顾名思义就是修改次数,对HashMap 内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 Map:注意到 modCount 声明为 volatile,保证线程之间修改的可见性。

所以在这里和大家建议,当大家遍历那些非线程安全的数据结构时,尽量使用迭代器

本博客有参考:http://blog.csdn.net/jzhf2012/article/details/8540410

http://zhangshixi.iteye.com/blog/674856

http://www.tuicool.com/articles/Evu2IzF

http://blog.csdn.net/u012926924/article/details/50452411

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值