第二章 ArrayList源码解析
1、ArrayList的创建(构造器)
常见的两种方式:
List<String> strList = new ArrayList<String>(); List<String> strList2 = new ArrayList<String>(2);
基本属性:
//对象数组:ArrayList的底层数据结构 private transient Object[] elementData; //elementData中已存放的元素的个数,注意:不是elementData的容量 private int size;
- ArrayList实现了java.io.Serializable,即采用Java默认的序列化机制。
- elementData(Object数组)是ArrayList的底层实现,使用transient关键字修饰,说明该属性不采用Java默认的序列化机制,ArrayList自己实现了序列化和反序列化的方法,见下面代码。
/** * Save the state of the <tt>ArrayList</tt> instance to a stream (that * is, serialize it). * * @serialData The length of the array backing the <tt>ArrayList</tt> * instance is emitted (int), followed by all of its elements * (each an <tt>Object</tt>) in the proper order. */ 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 size as capacity for behavioural compatibility with clone() s.writeInt(size); // 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(); } } /** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity int capacity = calculateCapacity(elementData, size); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity); ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
从上边源码可以看出,先调用java.io.ObjectOutptuStream的defaultWriterObject方法,进行默认的序列化操作,用transient修饰的字段没有被序列化。
- 好处:ArrayList会开辟多余的空间来保存数据,序列化和反序列化这些没有存放数据的空间会消耗更多资源,所以ArrayList的数组用transient修饰,告诉虚拟机不采用默认的序列化机制,使用自己重写的序列化和反序列化方法,仅仅序列化已经存放的数据。
构造器:
/** * 创建一个容量为initialCapacity的空的(size==0)对象数组 */ public ArrayList(int initialCapacity) { super();//即父类protected AbstractList() {} if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity:" + initialCapacity); this.elementData = new Object[initialCapacity]; } /** * 默认初始化一个容量为10的对象数组 */ public ArrayList() { this(10);//即上边的public ArrayList(int initialCapacity){}构造器 }
- 上边有参构造器中super()调用的ArrayList的父类AbstractList的构造器:
protected AbstractList() { }
实际应用中,如果我们能判断出所需的ArrayList的大小,有两个好处:
- 节省内存空间,如果我们只需要存储两个对象到容器中,就 new ArrayList(2);
避免扩容引起的性能消耗(扩容下面会讲到)。
2、get()和set()方法
get(int index)源码:
/** * 按照索引查询对象E */ public E get(int index) { RangeCheck(index);//检查索引范围 return (E) elementData[index];//返回元素,并将Object转型为E }
set(int index, E element)源码:
/** * 更换特定位置index上的元素为element,返回该位置上的旧值 */ public E set(int index, E element) { RangeCheck(index);//检查索引范围 E oldValue = (E) elementData[index];//旧值 elementData[index] = element;//该位置替换为新值 return oldValue;//返回旧值 }
/** * 检查索引index是否超出size-1 */ private void RangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size); }
- get()和set()方法都对索引index进行了检查(调用RangeCheck()方法),是为了详细报错信息并且缩小检查范围(index < 0 || index >= size,size是数组已存放数据数量),对于数组而言,如果index不满足要求(index < 0 || index > length,length是数组容量),会抛出下标越界异常,但如果length是10,size = 2,get(9)会返回null,这也是为什么缩小检查范围的原因;
- get()和set()方法都可以在常数时间内完成。
3、add()和addAll()方法
add(E element)和add(int index, E element)源码:
/** * 紧接着数组中最后一个数据后插入 * */ public boolean add(E e) { ensureCapacityInternal(size + 1); // 确保数组有足够容量,不够扩容 elementData[size++] = e; return true; }
/** * index索引处插入数据 * */ public void add(int index, E element) { rangeCheckForAdd(index); //检查索引是否满足条件 ensureCapacityInternal(size + 1); // 确保数组容量够用,不够扩容 System.arraycopy(elementData, index, elementData, index + 1, size - index); //将index及index后的元素后移,空出index位置 elementData[index] = element; size++; }
private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); //原来的1.5倍 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); //扩展空间并复制 }
- 从源码看,插入数据时,都会先先检查是否需要扩容,如果需要通过grow()方法完成。
ArrayList自动扩容过程:
- new一个容量为new_capacity的对象数组;
- 再将old_elementData数组里数据复制到新数组elementData中。
空间问题解决,开始插入数据:
- add(E element)方法是在最后插入(index = size处)
- add(int index, E element)需要先将index及index后的元素后移,让出index位置,再将数据插入到index位置,该方法有着线性的时间复杂度。