ArrayList中的构造方法源码在面试中被问到了...抱歉没准备好!!!告辞

点赞在看,养成习惯。

点赞收藏,人生辉煌。

点击关注【微信搜索公众号:编程背锅侠】,防止迷路。

转载请标注来源出处。

前言

看源码血泪史

刚开始工作面试的时候,面试官经常问ArrayList源码相关的问题,基本上都是这部分很快结束战斗。

  • 面试官:你看过ArrayList的源码吗?
  • 我:你肯定会说看过呀。
  • 面试官:那你来讲讲你对ArrayList源码的理解吧。
  • 我:底层的数据结构是object数组;增删快、查询慢等等,没说几句就完了。

其实看了ArrayList的源码以后,你会发现能说的点还是有很多的。
比如ArrayList的构造方法的底层数组真的是构造了一个长度为10的数组吗?
Arrays.copy方法,grow扩容方法是怎么扩容的?等等都可以细说。
ArrayList的源码从工作到现在大概看了不下10遍,这其中包括看了半道放弃的。
刚开始看源码是在一些博客网站上看,看的稀里糊涂不是很明白,越看越想放弃。
后面看了一些公开课,跟着老师讲的视频看源码,看完之后感觉有点意思。但是看完之后,自己单独看还是有点吃力。
2020年4月份的时候看了一遍ArrayList源码并且每行都做了注释,整理在了有道上。
现在是七月初时隔两个月在再次看源码发现以前的笔记有部分是模糊、或者理解不正确的。
目前我发布出来的ArrayList源码是我一步一步DEBUG调试验证的源码。如果理解有问题看过之后,还请多多指教。

ArrayList系列文章

第一篇:ArrayList中的构造方法源码在面试中被问到了…抱歉没准备好!!!告辞
第二篇:面试官让我讲ArrayList中add、addAll方法的源码…我下次再来
第三篇:工作两年还没看过ArrayList中remove、removeAll、clear方法源码的都来报道吧
第四篇: 乱披风锤法锤炼ArrayList源码中的get、set、contains、isEmpty方法!!!肝起来

ArrayList集合底层数据结构

ArrayList集合介绍
  • 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<>();
}
验证是否构造一个初始容量为10的集合
@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);
}
验证是否构造一个我们指定初始容量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填充
}

创作不易, 非常欢迎大家的点赞、评论和关注(^_−)☆
你的点赞、评论以及关注是对我最大的支持和鼓励,而你的支持和鼓励
我继续创作高质量博客的动力 !!!

猜你喜欢

转载自blog.csdn.net/wildwolf_001/article/details/107087015