java集合之ArrayList源码分析
Posted 爱上口袋的天空
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java集合之ArrayList源码分析相关的知识,希望对你有一定的参考价值。
一、ArrayList简介
- ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。
- 该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能。
- ArrayList的用法和Vector向类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用。另外,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。
- 看一下ArrayList继承了哪些类和实现了哪些接口:
二、ArrayList的数据结构
分析一个类的时候,数据结构往往是它的灵魂所在,理解底层的数据结构其实就理解了该类的实现思路,具体的实现细节再具体分析。
ArrayList是基于数组的数据结构,与LinkedList相比,更加适合在查询多、增删操作少的场景下使用,并且它是非线程安全的,如果并发量比较大的场景,需要改用线程安全的版本或者用JUC包中的CopyOnWriteArrayList。
ArrayList的数据结构如下:
说明:底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对ArrayList类的实例的所有的操作底层都是基于数组的。
三、ArrayList的源码分析
1、首先看一下ArrayList的继承结构和层次关系
从上面可以看出ArrayList继承了AbstractList,实现了List、RandomAccess、Cloneable以及
Serializable
1.1、为什么要先继承AbstractList?
这里是有一个思想,接口中全都是抽象的方法,而抽象类中可以有抽象方法,还可以有具体的实现方法,正是利用了这一点,让AbstractList是实现接口中一些通用的方法,而具体的类如ArrayList就继承这个AbstractList类,拿到一些通用的方法,然后自己在实现一些自己特有的方法,这样一来,让代码更简洁,就继承结构最底层的类中通用的方法都抽取出来,先一起实现了,减少重复代码。所以一般看到一个类上面还有一个抽象类,应该就是这个作用。
1.2、我们会出现这样一个疑问,在查看了ArrayList的父类AbstractList也实现了List<E>接口,那为什么子类ArrayList还是去实现一遍呢?
这是想不通的地方,所以我就去查资料,有的人说是为了查看代码方便,使观看者一目了然,说法不一,但每一个让我感觉合理的,但是在stackOverFlow中找到了答案,这里其实很有趣。网址贴出来http://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete开发这个collection 的作者Josh说。这其实是一个mistake,因为他写这代码的时候觉得这个会有用处,但是其实并没什么用,但因为没什么影响,就一直留到了现在。
1.3、RandomAccess接口
这个是一个标记性接口,通过查看api文档,它的作用就是用来快速随机存取,有关效率的问题,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高,例如arrayList。
而没有实现该接口的话,使用Iterator来迭代,这样性能更高,例如linkedList。所以这个标记性只是为了让我们知道我们用什么样的方式去获取数据性能更好。
1.4、Cloneable接口:
实现了该接口,就可以使用Object.Clone()方法了。
1.5、Serializable接口:
实现该序列化接口,表明该类可以被序列化,什么是序列化?简单的说,就是能够从类变成字节流传输,然后还能从字节流变成原来的类。
2、ArrayList类中的属性
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable // 版本号 private static final long serialVersionUID = 8683452581122892189L; /** 缺省容量 * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** 空对象数组 * Shared empty array instance used for empty instances. */ private static final Object[] 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 = ; /** 元素数组 * 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 /** 实际元素大小,默认为0 * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size; //最大数组容量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
3、构造方法
ArrayList有三个构造方法,如下:
3.1、无参构造方法
/** 是个空的Object[], 将elementData初始化,elementData也是个Object[]类型。 * Constructs an empty list with an initial capacity of ten. */ public ArrayList() this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
注意:
transient是短暂的意思。对于transient 修饰的成员变量,在类的实例对象的序列化处理过程中会被忽略。 因此,transient变量不会贯穿对象的序列化和反序列化,生命周期仅存于调用者的内存中而不会写到磁盘里进行持久化。
为什么要用transient关键字?
在持久化对象时,对于一些特殊的数据成员(如用户的密码,银行卡号等),我们不想用序列化机制来保存它。为了在一个特定对象的一个成员变量上关闭序列化,可以在这个成员变量前加上关键字transient。
由上面可以发现使用无参构造方法创建ArrayList集合对象,底层默认是一个空数组
3.2、有参构造函数一
/** * 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) //将自定义的容量大小当成初始化elementData的大小 this.elementData = new Object[initialCapacity]; else if (initialCapacity == 0) //如果自定义的容量大小为0,就直接使用空Object数组对象 this.elementData = EMPTY_ELEMENTDATA; else //判断如果自定义大小的容量小于0,则报下面这个非法数据异常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
3.3、有参构造方法二
/** * 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) elementData = c.toArray();//转换为数组 if ((size = elementData.length) != 0) //数组中的数据个数不为0时 // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) //每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么久需要使用ArrayList中的方法去改造一下 elementData = Arrays.copyOf(elementData, size, Object[].class); else // replace with empty array.如果为0直接使用空数组赋值给elementData this.elementData = EMPTY_ELEMENTDATA;
总结:arrayList的构造方法就做一件事情,就是初始化一下储存数据的容器,其实本质上就是一个数组,在其中就叫elementData。
4、核心方法
4.1、add()方法,有四个
1)boolean add(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) //确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了 ensureCapacityInternal(size + 1); // Increments modCount!! //在数据中正确的位置上放上元素e,并且size++ elementData[size++] = e; return true;
分析:ensureCapacityInternal(xxx); 确定内部容量的方法
private void ensureCapacityInternal(int minCapacity) if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) //看,判断初始化的elementData是不是空的数组,也就是没有长度,但是带这里,还没有真正的初始化这个elementData的大小。 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); //确认实际的容量,上面只是将minCapacity=10,这个方法就是真正的判断elementData是否够用 ensureExplicitCapacity(minCapacity);
分析ensureExplicitCapacity(int minCapacity)方法:
private void ensureExplicitCapacity(int minCapacity) modCount++;//记录修改集合的次数 // overflow-conscious code minCapacity如果大于了实际elementData的长度,那么就说明elementData数组的长度不够用,不够用那么就要增加elementData的length if (minCapacity - elementData.length > 0) grow(minCapacity);//arrayList能自动扩展大小的关键方法就在这里了
grow(int minCapacity); arrayList核心的方法,能扩展数组大小的真正秘密。
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) // overflow-conscious code int oldCapacity = elementData.length;//将扩充前的elementData大小给oldCapacity int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity if (newCapacity - minCapacity < 0)//这句话就是适应于elementData就空数组的时候,length=0,那么oldCapacity=0,newCapacity=0,所以这个判断成立,在这里就是真正的初始化elementData的大小了,就是为10.前面的工作都是准备工作。 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0)//如果newCapacity超过了最大的容量限制,就调用hugeCapacity,也就是将能给的最大值给newCapacity newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); //新的容量大小已经确定好了,就copy数组,改变容量大小咯。
分析:hugeCapacity(int minCapacity);
private static int hugeCapacity(int minCapacity) if (minCapacity < 0) // overflow,当minCapacity小于0,那么抛出异常,容量不可为负数 throw new OutOfMemoryError(); //如果minCapacity都大于MAX_ARRAY_SIZE,那么就Integer.MAX_VALUE返回,反之将MAX_ARRAY_SIZE返回。因为maxCapacity是三倍的minCapacity,可能扩充的太大了,就用minCapacity来判断了。 //Integer.MAX_VALUE:2147483647 MAX_ARRAY_SIZE:2147483639 也就是说最大也就能给到第一个数值。还是超过了这个限制,就要溢出了。相当于arraylist给了两层防护。 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
2)add(int index, E element)//在特定位置添加元素,也就是插入元素
/** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException @inheritDoc */ public void add(int index, E element) //校验index也就是插入的位置是否合理。 rangeCheckForAdd(index); //确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了 ensureCapacityInternal(size + 1); // Increments modCount!! //这个方法就是用来将index之后的元素都往后移一位, System.arraycopy(elementData, index, elementData, index + 1, size - index); //在目标位置上存放元素 elementData[index] = element; size++;
分析:rangeCheckForAdd(index)
/** * A version of rangeCheck used by add and addAll. */ private void rangeCheckForAdd(int index) if (index > size || index < 0)//插入的位置肯定不能大于size 和小于0,如果是,就报这个越界异常 throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
3)、addAll(Collection<? extends E> c):批量插入元素
/** * Appends all of the elements in the specified collection to the end of * this list, in the order that they are returned by the * specified collection's Iterator. The behavior of this operation is * undefined if the specified collection is modified while the operation * is in progress. (This implies that the behavior of this call is * undefined if the specified collection is this list, and this * list is nonempty.) * * @param c collection containing elements to be added to this list * @return <tt>true</tt> if this list changed as a result of the call * @throws NullPointerException if the specified collection is null */ public boolean addAll(Collection<? extends E> c) Object[] a = c.toArray();//将集合转换为数组 int numNew = a.length;//获取a数组的长度 //确定内部容量是否够了,size是数组中数据的个数,因为要添加numNew个元素,所以size+numNew,先判断size+numNew的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了 ensureCapacityInternal(size + numNew); // Increments modCount //表示将a数组从0位置开始拷贝,将numNew个元素拷贝到elementData数组中,位置是从size开始, System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0;
4)、addAll(int index, Collection<? extends E> c):在指定的位置后面批量插入元素
/** * Inserts all of the elements in the specified collection into this * list, starting at the specified position. Shifts the element * currently at that position (if any) and any subsequent elements to * the right (increases their indices). The new elements will appear * in the list in the order that they are returned by the * specified collection's iterator. * * @param index index at which to insert the first element from the * specified collection * @param c collection containing elements to be added to this list * @return <tt>true</tt> if this list changed as a result of the call * @throws IndexOutOfBoundsException @inheritDoc * @throws NullPointerException if the specified collection is null */ public boolean addAll(int index, Collection<? extends E> c) rangeCheckForAdd(index);//检测插入的位置是否合理 Object[] a = c.toArray();//将集合转换为数组 int numNew = a.length;//获取a数组的长度 //确定内部容量是否够了,size是数组中数据的个数,因为要添加numNew个元素,所以size+numNew,先判断size+numNew的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了 ensureCapacityInternal(size + numNew); // Increments modCount //表示需要插入的集合元素个数 int numMoved = size - index; if (numMoved > 0) //表示在elementData数组中从index位置开始元素依次向后移动,index元素移动到index + numNew的位置,后面元素紧跟其后 System.arraycopy(elementData, index, elementData, index + numNew, numMoved); //将a数组从0位置开始拷贝到elementData数组中,0对应index位置,总共拷贝numNew个元素 System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0;
例如:
List a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
List b = [a, b, c]
将b插入到a的index为3的位置,结果如下:
List a = [1, 2, 3, a, b, c, 4, 5, 6, 7, 8, 9, 10]
总结:
1)、无参构造初始化ArrayList集合时,默认是一个空数组,容量为0,但是添加第一个元素时,初始化容量为10,后面继续添加元素,如果容量不够,那么每次扩容1.5倍
2)、如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity,后面继续添加元素,如果容量不够,那么每次扩容1.5倍
当我们调用add方法时,实际上的函数调用如下:
4.2、删除方法
可以看到上面总共有5个删除方法,大同小异,下面我们挑选几个具体看看有什么不同:
4.2.1 、public E remove(int index)方法,根据下标删除指定的元素
/** 根据下标删除指定的元素 * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException @inheritDoc */ public E remove(int index) rangeCheck(index);//检测要删除的下标是否在集合的范围内 modCount++;//增加修改集合的次数 E oldValue = elementData(index);//从elementData数组中获取当前下标下的元素 int numMoved = size - index - 1;//表示index元素后面还剩余的元素个数 if (numMoved > 0) //下面表示将index+1的元素全部向前移动一位 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; //将size减一,并且size的位数置为空,让gc(垃圾回收机制)更快的回收它。 return oldValue;//返回要删除的元素值
/** * Checks if the given index is in range. If not, throws an appropriate * runtime exception. This method does *not* check if the index is * negative: It is always used immediately prior to an array access, * which throws an ArrayIndexOutOfBoundsException if index is negative. */ private void rangeCheck(int index) if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
4.2.12、public E remove(Object o)方法,删除指定的元素
/** * Removes the first occurrence of the specified element from this list, * if it is present. If the list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index * <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> * (if such an element exists). Returns <tt>true</tt> if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present * @return <tt>true</tt> if this list contained the specified element */ public boolean remove(Object o) if (o == null) //判断要删除的元素是否为空,如果为空走下面的方法 for (int index = 0; index < size; index++)//循环集合 if (elementData[index] == null) //满足为空的元素,删除第一个为null的下标元素即可 fastRemove(index);//fastRemove(index)方法的内部跟remove(index)的实现几乎一样,这里最主要是知道arrayList可以存储null值 return true; else //下面的方法几乎和上面一样 for (int index = 0; index < size; index++) if (o.equals(elementData[index])) fastRemove(index); return true; return false;
4.2.13、public boolean removeAll(Collection<?> c),批量删除元素
/** * Removes from this list all of its elements that are contained in the * specified collection. * * @param c collection containing elements to be removed from this list * @return @code true if this list changed as a result of the call * @throws ClassCastException if the class of an element of this list * is incompatible with the specified collection * (<a href="Collection.html#optional-restrictions">optional</a>) * @throws NullPointerException if this list contains a null element and the * specified collection does not permit null elements * (<a href="Collection.html#optional-restrictions">optional</a>), * or if the specified collection is null * @see Collection#contains(Object) */ public boolean removeAll(Collection<?> c) Objects.requireNonNull(c);//判断集合c必须非空 return batchRemove(c, false);//批量删除c集合元素
private boolean batchRemove(Collection<?> c, boolean complement) final Object[] elementData = this.elementData;//将原集合赋值给新的elementData数组变量 int r = 0, w = 0;//r用来控制循环,w是记录有多少个差集 boolean modified = false; try for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r];//检测如果elementData数组中的元素不在c集合中就使用w下标重新存储到elementData数组中 finally // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) //如果contains方法使用过程报异常,用来处理这种问题的 //下面是将elementData数组的r下标开始后面的元素都向前移动,r对应的位置是w,后面依次移动到w后面的位置 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; if (w != size) //w代表elementData中元素不存在于c集合中的个数,表示差集数量 // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null;//将w之后下标的都置空,有利于GC回收 modCount += size - w;//记录修改次数 size = w;//更新elementData数组大小 modified = true;//表示修改完成 return modified;
4.3、set方法
/** * Replaces the element at the specified position in this list with * the specified element. * * @param index index of the element to replace * @param element element to be stored at the specified position * @return the element previously at the specified position * @throws IndexOutOfBoundsException @inheritDoc */ public E set(int index, E element) rangeCheck(index);//检测下标是否在集合范围内 E oldValue = elementData(index);//取出集合的index下标值 elementData[index] = element;//将element值重新赋给elementData数组的index位置 return oldValue;//最后返回旧值
四、总结
- arrayList可以存放null。
- arrayList本质上就是一个elementData数组。
- arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
- arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素。
- arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
- arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。
以上是关于java集合之ArrayList源码分析的主要内容,如果未能解决你的问题,请参考以下文章