JAVA集合源码攻坚战(7)——ArrayList

前言

前面都是分析了一些接口或抽象类。是不是感觉很枯燥?那么今天就来看看十分常用的ArrayList,看看它到底是如何来实现前面说的那些接口或者抽象类的。

ArrayList

数据结构

我们说一个集合,不得不提的就是他存储的数据结构到底是怎样的。
ArrayList,通过名字就能看出,是Array + List,虽然不能等同,但是能从这方便去思考。
ArrayList底层就是数组一个数组结构,但是他是可以扩容的,不像数组一样大小固定。
好,言归正传,还是一起来分析源码吧~~

整体介绍

ArrayList继承了AbstractList,实现了List,RandomAccess,Cloneable,Serializable接口。
ArrayList是对List的一个可调整大小的实现类,实现了List所有的可选操作,而且允许所有元素的存储,包括NULL值。
然后从时间复杂度上来看,size,isEmpty,get,set,iterator和listIterator操作方法的时间复杂度都为O(1),而add操作的时间复杂度为O(n);其他的操作粗略的来看都需要线性的时间,具体看源码再分析。

属性

ArrayList有7个属性,一个一个来看。
在这里插入图片描述
1、serialVersionUID
不多说,序列化和反序列化的标识。
2、DEFAULT_CAPACITY

/**
     * 默认的初始化大小
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

3、EMPTY_ELEMENTDATA

/**
     * 用于空实例的共享空数组实例。
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

4、EFAULTCAPACITY_EMPTY_ELEMENTDATA

/**
     * 用于默认大小的空实例的共享空数据实例。
     * 这个区别于EMPTY_ELEMENTDATA,是因为想知道当首个元素被加入的时候,需要扩大多少。
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

5、elementData

/**
     * 数组缓冲到ArrayList元素存储的地方。
     * ArrayList的容量就是这个数组缓冲区的长度。
     * 任何elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList会在第一个元素被添加的时候自动扩展到DEFAULT_CAPACITY(默认容量)
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

6、size

/**
     * ArrayList的大小,即包含的元素的个数
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

7、MAX_ARRAY_SIZE

    /**
     * 数组允许的最大尺寸
     * The maximum size of array to allocate.
     * 一些虚拟机会在数组里保留一些头信息,所以需要预留一部分空间来存放这些头信息。
     * Some VMs reserve some header words in an array.
     * 尝试分配一个更大的数组可能导致OutOfMemoryError错误。
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

方法

构造函数
    /**
     * 指定初始容量的空list的构造器
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
    	// 根据给定的容量大小值进行初始化
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 构造一个list,包含了指定的集合的所有元素,按照指定集合的迭代器返回的顺序。
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
    	// 将元素通过数组形式复制到目标list的数组缓冲区中
        elementData = c.toArray();
        if ((size = elementData.length) != 0) { // 判断数组大小是否为0
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
        	// c.toArray 可能返回的不是一个Object对象的数组,这时要通过Arrays.copyOf方法来创建一个Object数组。
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

这里有一点不是很明白,就是第三个构造函数中,调用了c.toArray()方法后,为什么下面还要考虑是不是Object对象数组,c.toArray()方法不是本身返回的就是Object对象数组吗?暂时没弄明白。

容量操作相关

1、public void trimToSize()
释放没有被元素占用的那些空余的空间,一般应该是确定了不会再有新增元素之后,再调用比较好,不然一旦有新增操作,还要扩容,反而影响性能。主要看是要空间还是要性能。

    /**
     * 修剪ArrayList实例的容量,改为与当前实际元素大小相等,避免空间浪费。通过调用该方法,能最小化ArrayList实例占用的空间
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
    	// 对结构的一种操作,需要检查并发
        modCount++;
        // 如果元素个数比数组空间小,则要将元素按size尺寸,拷贝到一个新的数组里,释放多余空间。
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

2、public void ensureCapacity(int minCapacity)
主动扩容操作,可以手动调用该方法来达到扩容的目的

    /**
     * 扩大ArrayList实例的容量,如果必要,至少要能承受住minCapacity参数指定的元素个数
     * Increases the capacity of this <tt>ArrayList</tt> instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param   minCapacity   the desired minimum capacity
     */
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table 判断有没有添加过元素
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            // 默认的容量
            : DEFAULT_CAPACITY;
        
        // 需要的容量比默认的大时,需要扩容
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    private void ensureExplicitCapacity(int minCapacity) {
    	// 扩容是改变底层结构的操作,需要修改modCount,检查并发
        modCount++;

        // overflow-conscious code
        // 指定的扩容容量比当前元素个数大时。
        if (minCapacity - elementData.length > 0)
        	// 扩容操作
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
    	// 保存旧的数组的长度
        int oldCapacity = elementData.length;
        // 获得新的容量 = oldCapacity*1.5 ,就是扩容为原来的1.5倍,这里用了位操作,提高性能。
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity; 		// 如果原来的基础上扩容1.5倍,还是小于给定的容量,就取给定的容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);	// 如果新的容量超过了最大容量,则申请更大容量
        // minCapacity is usually close to size, so this is a win:
        // 一般来说,minCapacity是程序员根据元素个数来定的,比较接近于实际的元素个数,而使用newCapacity则会更宽松,多余空间更多。
        // 比如原来容量为10,用户申请了容量为12,但是实际上容量是会扩容到15的,而不是12。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // 当所需容量大于最大允许容量时,可以申请Integer.MAX_VALUE的容量。
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

一个扩容操作,竟然涉及到这么多,需要考虑的方方面面还是很多的。
ensureCapacity方法是供用户来调用的,但实际的扩容操作是在最后一个grow方法里的。
这里有一点需要特别注意,用户主动调用ensureCapacity方法来进行扩容时,并不是指定了扩容多少就会扩容多少的,这要看实际执行扩容的grow方法。
举个测试栗子:

public class TestEnsureCapacity {

	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<>();
		System.out.println(list.size());
		for (int i = 0; i < 8; i++) {
			list.add("aaa");
		} 
		
		System.out.println(list.size());
		
		list.ensureCapacity(13);
		System.out.println(list.size());
	}
}

这里要执行断点调试才能看出效果,因为size返回的只是元素实际个数,看不出容量大小。
在这里插入图片描述
看到,还没扩容前,是默认容量10,元素个数为8。
在这里插入图片描述
扩容后,可以看到,数组容量变为了15,而不是我们指定的13。
再来看如果指定的容量大于旧容量的1.5倍,会怎么样呢?
在这里插入图片描述
可以看到,这里就是我们指定多少就是多少了。

数据操作相关

1、拷贝操作

    /**
     * 返回对Arraylist实例的一个浅拷贝(元素本身不被拷贝,即两个list同个位置的元素指向同一个地址)
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) 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(e);
        }
    }

所谓浅拷贝,这里给个测试栗子:

package com.test.arraylist;

import java.util.ArrayList;

public class TestClone {

	public static void main(String[] args) {
		ArrayList<Person> a = new ArrayList<>();
		
		Person p1 = new Person("xiaoming", 1);
		Person p2 = new Person("liming", 2);
		
		a.add(p1);
		a.add(p2);
		System.out.println("没操作的a");
		for (Person person : a) {
			System.out.println(person);
		}
		
		ArrayList<Person> b = (ArrayList<Person>) a.clone();
		
		System.out.println("拷贝后的b");
		for (Person person : b) {
			System.out.println(person);
		}
		for (Person person : b) {
			if(person.getAge() == 1){
				person.setAge(444);
			}
		}
		System.out.println("修改后的b");
		for (Person person : b) {
			System.out.println(person);
		}
		System.out.println("没操作的a");
		for (Person person : a) {
			System.out.println(person);
		}
		
	}
}

class Person{
	private String name;
	private int age;
	
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return this.name + "--" +this.age;
	}
	
}

结果如下:
在这里插入图片描述
所以可以看到,b中的Person对象实际上就是a中的对象,两个指向的对象地址是一致的,改变了其中一个,另一个中也会改变。

2、转换为数组

    /**
     * 返回一个包含了list中的所有元素的数组,并且顺序一致。
     * Returns an array containing all of the elements in this list
     * in proper sequence (from first to last element).
     * 
     * 返回的数组与原来的list无关。换言之,该方法必须新分配一个新的数组。这样调用者就可以放心地修改返回的数组了。
     * <p>The returned array will be "safe" in that no references to it are
     * maintained by this list.  (In other words, this method must allocate
     * a new array).  The caller is thus free to modify the returned array.
     *
     * <p>This method acts as bridge between array-based and collection-based
     * APIs.
     *
     * @return an array containing all of the elements in this list in
     *         proper sequence
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    /**
     * Returns an array containing all of the elements in this list in proper
     * sequence (from first to last element); the runtime type of the returned
     * array is that of the specified array.  If the list fits in the
     * specified array, it is returned therein.  Otherwise, a new array is
     * allocated with the runtime type of the specified array and the size of
     * this list.
     *
     * <p>If the list fits in the specified array with room to spare
     * (i.e., the array has more elements than the list), the element in
     * the array immediately following the end of the collection is set to
     * <tt>null</tt>.  (This is useful in determining the length of the
     * list <i>only</i> if the caller knows that the list does not contain
     * any null elements.)
     *
     * @param a the array into which the elements of the list are to
     *          be stored, if it is big enough; otherwise, a new array of the
     *          same runtime type is allocated for this purpose.
     * @return an array containing the elements of the list
     * @throws ArrayStoreException if the runtime type of the specified array
     *         is not a supertype of the runtime type of every element in
     *         this list
     * @throws NullPointerException if the specified array is null
     */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)	// 如果给定的数据长度小于实际元素个数,则需要新建数组。
            // Make a new array of a's runtime type, but my contents:
        	// 创建一个运行时类的新数组,尺寸按照实际元素个数,这里调用了Arrays.copyOf方法。
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
        	// 如果给定的数组在填充数据后,有多余空间,则在紧跟集合结束的那个数组元素后面位置设置为null,
        	// 这是在知晓其他元素都不可能为null的情况下,可以用来标识元素实际个数的长度。
            a[size] = null;
        return a;
    }

3、增删改查一系列操作

  1. add(E e)
    /**
     * 在list的末尾追加元素
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
    	// 容量确认,如果不够则扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	// 一开始数组是空的,容量为0,直到第一次添加元素,才会进行容量初始化,其实也可以看做边界检查的一种。从一个元素也没有到有元素的质变
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureCapacityInternal(int minCapacity) {
    	// 先通过calculateCapacity方法计算容量
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
  1. add(Index, E)
    public void add(int index, E element) {
    	// 索引检查
        rangeCheckForAdd(index);

        // 容量检查
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 先将指定索引后面的元素搬移
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

至于addAll的两个方法就不赘述了,其实原来都一样。

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
    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 void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

    /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

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

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

内部类

内部实现了两个迭代器Itr和ListItr,和一个SubList,功能上来说,可我们前面介绍的AbstractList差不多,没实现什么特别的功能。

java 8 新增的内容

实现了一个静态类ArrayListSpliterator

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值