List
接口的可调整大小的数组实现。
数组:一旦初始化长度就不可以发生改变 。
增删慢:每次删除元素,都需要更改数组长度、拷贝以及移动元素位置。
查询快:由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。
private static final int DEFAULT_CAPACITY = 10; 复制代码
private static final Object[] EMPTY_ELEMENTDATA = {}; 复制代码
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 复制代码
ArrayList
的底层数据结构, transient
表示该字段不进行序列化操作 transient Object[] elementData; 复制代码
ArrayList
的大小,就是集合中元素的个数 private int size; 复制代码
Constructor | Constructor描述 |
---|---|
ArrayList() | 构造一个初始容量为十的空列表。 |
ArrayList(int initialCapacity) | 构造具有指定初始容量的空列表。 |
ArrayList(Collection<? extends E> c) | 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。 |
ArrayList()
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 复制代码
@Test public void test_get(){ // 运行这行代码真的会构造一个初始容量为10的集合吗? List<Integer> list = new ArrayList<>(); } 复制代码
@Test public void test_get(){ try { List<String> list = new ArrayList<>(); Field field = list.getClass().getDeclaredField("elementData"); field.setAccessible(true); int size = ((Object[]) field.get(list)).length; System.out.println(size);// 0 事实证明并没有初始化一个容量为10的集合 // 添加第一个元素 list.add("1"); Field field2 = list.getClass().getDeclaredField("elementData"); field2.setAccessible(true); int size2 = ((Object[]) field2.get(list)).length; System.out.println(size2);// 10 事实证明添加第一个元素的时候才进行初始化容量10 } catch (Exception e) { e.printStackTrace(); } } 复制代码
结论:通过以上的代码演示证明空参构造方法创建集合对象并未构造一个初始容量为十的空列表,仅仅将DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的地址赋值给elementData。只有添加第一个元素的时候才会将容量初始化为10
ArrayList(int initialCapacity)
public ArrayList(int initialCapacity) { // 容量大于0,按照指定的容量初始化数组 if (initialCapacity > 0) { // 创建一个数组,且指定长度为initialCapacity this.elementData = new Object[initialCapacity]; // 如果initialCapacity容量为0,把EMPTY_ELEMENTDATA的地址赋值给elementData } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; // 容量小于0,抛非法异常 } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } 复制代码
@Test public void test_c(){ // 创建一个带有初始容量的集合,这个集合创建以后真的初始容量为5吗? List<Integer> list = new ArrayList<>(5); } 复制代码
@Test public void test_get(){ try { // 构造一个长度为5的集合 List<String> list = new ArrayList<>(5); Field field = list.getClass().getDeclaredField("elementData"); field.setAccessible(true); int size = ((Object[]) field.get(list)).length; System.out.println(size);// 5 确实是我们自己指定的容量 } catch (Exception e) { e.printStackTrace(); } } 复制代码
结论:根据 ArrayList
构造方法参数创建指定长度的数组。
按照集合迭代器返回的顺序,构造一个list包含指定集合的元素。c参数:元素将被放到list中的集合指定的集合为null,将会抛出 NullPointerException
ArrayList(Collection<? extends E> c)
public ArrayList(Collection<? extends E> c) { // 将给定的集合对象转成数组,且将数组的地址赋值给elementData elementData = c.toArray(); // 将elementData的长度赋值给集合长度size,且判断是否不等于 0 if ((size = elementData.length) != 0) { // 判断elementData 和 Object[] 是否为不一样的类型 // c.toArray()数组不是object数组进行转换成Object[] // 每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么就需要使用ArrayList中的方法去改造一下。 // elementData.getClass()到底是什么类型的?下面搞个例子测试一下 if (elementData.getClass() != Object[].class) // 转换Object[] 【在我的其他文章中的新增源码里面有分析】 // 如果不一样,使用Arrays的copyOf方法进行元素的拷贝 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 给定的数组的长度为0,用空数组代替 this.elementData = EMPTY_ELEMENTDATA; } } 复制代码
toArray()
方法及其实现源码 // Collection<E>接口中转数组接口 // 返回一个包含此集合中所有元素的数组。这个方法可以保证给定集合的顺序返回数组。此方法充当基于数组的API和基于集合的API之间的桥梁。 Object[] toArray(); // ArrayList<E>中的toArray()方法 public Object[] toArray() { // 调用数组工具类方法进行拷贝 return Arrays.copyOf(elementData, size); } 复制代码
Arrays类中的copyOf方法
源码 // Arrays类中的copyOf方法进行数组的拷贝。original原始的数组,newLength新的容量 public static <T> T[] copyOf(T[] original, int newLength) { // 再次调用方法进行拷贝 return (T[]) copyOf(original, newLength, original.getClass()); } // 将原始的数组copy到新的容量的数组中的具体实现 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") // 用三元运算符进行判断,不管结果如何都是创建一个新数组 T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); // 将数组的内容拷贝到 copy 该数组中,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位 System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); // 返回拷贝元素成功后的数组 return copy; } 复制代码
arraycopy
方法 /* * @param src the source array.原始的数组 * @param srcPos starting position in the source array.在原始数组中开始的位置 * @param dest the destination array.目标数组 * @param destPos starting position in the destination data.在目标数组中的起始位置 * @param length the number of array elements to be copied.要copy的元素的个数 */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); 复制代码
elementData.getClass()
到底是什么类型 @Test public void test_c(){ List<Integer> list = new ArrayList<>(); list.add(1); // getClass()这个方法返回的是该对象的运行时类。 Class<? extends Object[]> aClass = list.toArray().getClass(); System.out.println(aClass);// class [Ljava.lang.Object; if (aClass != Object[].class){ System.out.println(false); }else { System.out.println(true);// 打印结果:true } } 复制代码
Arrays.copyOf方法
接下来验证一下 @Test public void test_arrays_copy_of(){ Object[] str = new Object[]{"1", "2"}; // 1代表的是要拷贝元素的个数 Object[] arr_ = Arrays.copyOf(str, 1); System.out.println(Arrays.toString(arr_)); // [1] Object[] str0 = new Object[]{"3", "4"}; Object[] arr0_ = Arrays.copyOf(str0, 2); System.out.println(Arrays.toString(arr0_)); // [3, 4] String[] str1 = new String[]{"5", "6"}; String[] arr1_ = Arrays.copyOf(str1, 5); System.out.println(Arrays.toString(arr1_)); //[5, 6, null, null, null] 元素不够会用null填充 } 复制代码