ArrayList概述
ArrayList底层由数组实现,非线程安全,但是数组可以动态增加,也可以叫动态数组,提供了一系列的好处,我们来深入看看:
成员变量与构造函数
/** * 存储ArrayList内的数组 */ private transient Object[] elementData; /** * The size of the ArrayList (the number of elements it contains). * ArrayList中包含了多少元素 */ private int size; /** * 给定ArrayList的容量 ,如果给定容量小于0,则抛出异常 */ public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } /** * 默认的空的构造函数,默认大小为10. */ public ArrayList() { this(10); } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * 传入的参数是一个集合,将集合转换为数组放入成员变量中,设置大小size * */ 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); }
这是jdk的一个bug,编号是6260652,因为toArray内部使用的是clone,所以elementData 的返回类型有可能不是Object,所以需要判断一下。我们来看一个例子,何时回
List<String> a1 = Arrays.asList("123"); //class [Ljava.lang.String; System.out.println(a1.toArray().getClass()); ArrayList<String> a2 = new ArrayList<String>(); //class [Ljava.lang.Object; System.out.println(a2.toArray().getClass()); //我们会发现a1和a2都是通过toArray方法,为什么返回的对象不一样? //我们分别输出一下a1和a2所属的类 //class java.util.Arrays$ArrayList System.out.println(a1.getClass()); //class java.util.ArrayList System.out.println(a2.getClass());
傻眼了,这俩根本不是一个类,a1是Arrays的内部类,所以调用的toArray方法也就不一样,a1是调用的clone方法,a2则是调用了Arrays.copyOf(elementData, size)方法。所以结果必然不一样。
所以源码中使用if语句判断一次,确保数组的类型是Object类的,
而Arrays.copyOf(elementData, size, Object[].class);这是在创建一个Object数组。
// 将此 ArrayList 实例的容量调整为列表的当前大小。应用程序可以使用此操作来最小化 ArrayList 实例的存储量。 // 也就是减少数组中没必要的空间,留下存数据的单元。 public void trimToSize() { modCount++; int oldCapacity = elementData.length; if (size < oldCapacity) { elementData = Arrays.copyOf(elementData, size); } } //增加Arraylist中的容量,以确保可以容纳最小容量参数指定的元素个数 public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; //获取长度 if (minCapacity > oldCapacity) { //当最小容量参数大于原有容量,进行扩容 Object oldData[] = elementData; //oldData有什么用? //扩展50%的容量,加1是多出来赋值用。 int newCapacity = (oldCapacity * 3)/2 + 1; //如果还不够,则将minCapacity设置为当前容量 if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }
至于扩容为何加1:通俗的说就是,当复制到第11个数时(默认是10),需要扩容,但是第11个数还没有赋上去,所以多出来的1个空间是留给11的,但是没有赋值的空间便为50%;
上面代码中我们看到有一句话是看似没有用处:
Object oldData[] = elementData;
其实这句话的意思是创建了一个新的引用oldData指向了原数组,然后原来的引用elementData指向了copy后的新空间,有新的引用指向原内存就保证了再copy过程是安全的。否则jvm将这置为无用内存,被其余线程分配的内存侵占了,copy过程还没完成,就严重了
// 返回数组的实际大小 public int size() { return size; } //判断数组是否为空 public boolean isEmpty() { return size == 0; } //判断数组中是否包含为o的对象,若返回的下标大于0则证明包含,若为-1则证明没找到 public boolean contains(Object o) { return indexOf(o) >= 0; } //返回数组中该元素的下标,若下标为正数或0证明存在此对象,且下标为返回的数;若返回的是-1,证明对象不在数组中 public int indexOf(Object o) { //o是否为null,分开进行判断 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; } //返回数组中最后一次出现该元素的下标,若返回值为-1,则说明没有 public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; } //返回一个ArrayList的浅克隆实例,里面的对象并没有被克隆 public Object clone() { try { ArrayList<E> v = (ArrayList<E>) super.clone(); //拷贝为新数组 v.elementData = Arrays.copyOf(elementData, size); //置修改次数为0 v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } } /** * 按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组。由于此列表不维护对返回数组的任何引用,因而它将是“安全的”。 * 也 就是说,此方法分配了一个新的数组。因此,调用者可以自由地修改返回的数组。 */ public Object[] toArray() { return Arrays.copyOf(elementData, size); } //和上面一样,只不过返回的类型是 该数组运行时的类型 public <T> T[] toArray(T[] a) { //若参数数组大小的长度小于原数组长度,则把原数组的长度作为参数数组的长度,创建数组 if (a.length < size) return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); //如果参数数组的长度大于原数组的长度,且后面还有剩余空间,则将最后一个数紧接的元素置为null用于确定数组的长度。 if (a.length > size) a[size] = null; return a; } //返回指定下标处的元素值 public E get(int index) { //检查下标是否越界 RangeCheck(index); return (E) elementData[index]; } //将指定下标处的元素,置为指定字符,返回以前的字符 public E set(int index, E element) { RangeCheck(index); E oldValue = (E) elementData[index]; elementData[index] = element; return oldValue; } // 将指定元素追加到集合后面 public boolean add(E e) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = e; return true; } // 将指定元素添加到指定下标处 public void add(int index, E element) { //如果指定的下标小于0或大于总长度,报告下标越界 if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); //看需不需要扩容 ensureCapacity(size+1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } //删除指定下标的元素 public E remove(int index) { RangeCheck(index); modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) //将后面的一次往前移动一个单元 System.arraycopy(elementData, index+1, elementData, index, numMoved); //如果要删除的是最后一个直接置为null elementData[--size] = null; // Let gc do its work return oldValue; } // 删除指定的对象 public boolean remove(Object o) { //如果指定对象为null 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; } //快速删除指定下标的元素,类似于remove(int 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 } //删除所有元素,也就是将所有元素都置为null public void clear() { modCount++; // Let gc do its work for (int i = 0; i < size; i++) elementData[i] = null; //大小设置为0 size = 0; } //将指定集合中的元素添加到此Arraylist中 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; } //将指定集合插入到指定开始位置的地方 public boolean addAll(int index, Collection<? extends E> c) { if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: " + index + ", Size: " + size); Object[] a = c.toArray(); int numNew = a.length; ensureCapacity(size + numNew); // Increments modCount int numMoved = size - index; //如果指定的位置是在最后一个元素之前 if (numMoved > 0) //先将从index开始的元素向后移动numNew System.arraycopy(elementData, index, elementData, index + numNew, numMoved); //将带插入的插入到中间 System.arraycopy(a, 0, elementData, index, numNew); size += numNew; //如果参数集合中长度为0就证明,没有变动,返回false return numNew != 0; } //移除列表中[fromIndex,toIndex)的元素,注意到是受保护的方法 protected void removeRange(int fromIndex, int toIndex) { modCount++; //移动的个数 int numMoved = size - toIndex; //后面的替换前面的 System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // Let gc do its work int newSize = size - (toIndex-fromIndex); //把后面原来的元素都置为null while (size != newSize) elementData[--size] = null; } // 检查所传进来的下标是否越界,私有 private void RangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); } //序列化 private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // 写入数组的总长度,即容量 s.writeInt(elementData.length); // 写入数组中的每一个元素 for (int i=0; i<size; i++) s.writeObject(elementData[i]); //当总的修改次数和期望的修改次数不相等时,就说明其他线程修改了集合,抛出异常,fail-fast机制 if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } // 读取Arraylist的容量以及原数组中每一个元素 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in size, and any hidden stuff s.defaultReadObject(); // Read in array length and allocate array int arrayLength = s.readInt(); //创建一个新数组 Object[] a = elementData = new Object[arrayLength]; // Read in all elements in the proper order. for (int i=0; i<size; i++) a[i] = s.readObject(); }
总结:
1、ArrayList的默认容量为10,在扩容时是扩大为原来容量的50%;
2、ArrayList无论是插入还是删除都会移动数组中的部分元素,特别耗时间;
3、ArrayList中元素允许为null,查找和删除时都会分开来判断;
4、源码中大量调用了Arrays.copyOf()和System.arraycopy()。我们查看Arrays源码会发现,显示复制了一份相同长度的数组,最后还是调用了 System.arraycopy()方法,但是System.arraycopy()方法为本地方法。
5、removeRange(int fromIndex, int toIndex)这个方法权限是受保护的,原因是什么?
看了这篇博文(ArrayList removeRange方法分析),得出的结论是java为了避免冗余,因为其父类AbstractList已经定义了此方法,放在内部使用此方法。
但是还是有点迷惑,不知道各位朋友有没有更清晰的见解。
具体可参见http://stackoverflow.com/questions/2289183/why-is-javas-abstractlists-removerange-method-protected
参考:
JDK1.6集合框架bug:c.toArray might (incorrectly) not return Object[] (see 6260652)
Java集合---ArrayList的实现原理
版权声明:本文为博主原创文章,转载请注明出处。