硬啃世界--------ArrayList源码解析

博客介绍了硬啃Java中ArrayList源码的过程。先说明了使用JDK 1.8版本及调试配置,接着分析了ArrayList添加元素的源码,包括初始化、扩容逻辑等。总结出ArrayList数据结构为数组,初始长度10,每次扩容为原大小一半,且非线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

欢迎来到硬啃世界

你好,希望你每天给自己一点信心和耐心,不做被公司、面试者、社会淘汰的程序员,在这里硬啃汉带你去硬啃源码重新捡回信心。

硬啃前的一些准备

  1. JDK版本我们用1.8的,现在还是主流的版本。
  2. 因为如果我们不做任何配置的话,我们debug的时候跳转到JDK源码是看不到我们传进去的值,这个原因可以百度一下,大致就是因为减少JDK的大小,配置步骤看如下帖子:
    解决Eclipse调试JDK源码无法查看变量值

开始硬啃

我们先写一个循环,循环很简单,就是不停的往List里面添加元素,至于为什么是循环11次,等会说

public static void main(String[] args) {
   	
   	List<String> list = new ArrayList<>();
   	String name = "哇哈哈";
   	for (int i = 0; i < 11; i++) {
   		if (i==10) {
   			list.add(name+i);
   		}else {
   			list.add(name+i);
   		}
   	}
}

然后把断点打到new ArrayList这行,然后运行main方法,然后我们进入这一句new的时候看看发生了什么

	
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData;
    
	public ArrayList() {
       this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
   }
  
  • 这里有个很少见的关键字:transient。
  • 主要作用就是当我们序列化这个对象的时候,该关键字不被序列化。

这里我们看到他是初始化了一个内部数组 elementData ,其实我们平时用的add、remove等方法都是在操作这个数组,所以ArrayList的底层数据结构就是个数组,这是重点面试会问。

我们断点接着走,进入add方法,我们看看添加元素的时候做了什么操作

	private int size;
	
	public boolean add(E e) {
       ensureCapacityInternal(size + 1);  // Increments modCount!!
       elementData[size++] = e;
       return true;
   }

这里先调用了ensureCapacityInternal方法,然后我们看到一个新的变量 size ,他是用来记录该ArrayList存在多少个元素的。

我们来看看ensureCapacityInternal内部逻辑:

    /**
    * Default initial capacity.
    */
   private static final int DEFAULT_CAPACITY = 10;
   
	private void ensureCapacityInternal(int minCapacity) {
       if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
           minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
       }

       ensureExplicitCapacity(minCapacity);
   }
   
   private void ensureExplicitCapacity(int minCapacity) {
       modCount++;

       // overflow-conscious code
       if (minCapacity - elementData.length > 0)
           grow(minCapacity);
   }
   
   private void grow(int minCapacity) {
       // overflow-conscious code
       int oldCapacity = elementData.length;
       int newCapacity = oldCapacity + (oldCapacity >> 1);
       if (newCapacity - minCapacity < 0)
           newCapacity = minCapacity;
       if (newCapacity - MAX_ARRAY_SIZE > 0)
           newCapacity = hugeCapacity(minCapacity);
       // minCapacity is usually close to size, so this is a win:
       elementData = Arrays.copyOf(elementData, newCapacity);
   }

我们按照顺序,从上到下,一个一个方法分析:

  1. 判断elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这里不就是刚开始初始化的时候那个空数组嘛,其实这里就是判断是否第一次操作这个ArrayList,如果不是就不会进入判断内的逻辑,然后是的话,就将DEFAULT_CAPACITY变量和当前minCapacity做比较,最大的值重新赋值给minCapacity,我们看到DEFAULT_CAPACITY是等于10的,后面再详说。
  2. 然后调用ensureExplicitCapacity() 方法,minCapacity作为入参传入。
  3. modCount++,这个的意思就是操作次数+1,modCount就是用来记录操作这个ArrayList的次数的。
  4. 然后判断minCapacity - elementData.length 是否大于0,换句话就是我准备添加的元素总数是否比你原来的数组大,如果是,放不下了,调用 grow() 方法开始扩容。
  5. 我们来看看如何扩容的,int oldCapacity = elementData.length,获取当前数组长度。
  6. 计算得到扩容后的大小newCapacityoldCapacity加上oldCapacity右移1位,这里采用了位的操作,比方说oldCapacity原本是10,右移之后就是5,那么这里的意思就是每次扩容都是原来的长度的一半,原本是10个元素,现在需要扩容,就是扩容15个长度,现在30个元素,需要扩容最终就是45个长度,以此类推。
  7. 然后判断newCapacity - minCapacity < 0,第一次添加元素的时候newCapacity是等于0的,0右移1还是0所以这里减minCapacity肯定会小于0,然后我们知道前面minCapacity 已经是等于10了,然后这里将minCapacity 赋值给newCapacity,这里就说明我们的ArrayList初始长度是等于10的,但是不代表现在大小就是10,前面也说过,size用来表示ArrayList当前有多少个元素,这里的长度只是说我们的这个ArrayList可以放多少个元素。
  8. 接着判断newCapacity - MAX_ARRAY_SIZE > 0,这里就是判断扩容后的长度是否超过最大允许的长度2147483639,我相信正常没人会用个ArrayList存这么多的元素,真有这样的人建议原地转行。
  9. 最后就是elementData = Arrays.copyOf(elementData, newCapacity) 就是帮我们创建一个newCapacity长度的新数组并把原来的elementData元素复制到新数组并重新赋值覆盖elementData
  10. 到此,判断是否扩容,然后到怎么扩容就结束了。

执行完ensureCapacityInternal方法判断是否需要扩容操作之后,回到add方法,接下来就很简单了,elementData[size++] = e,将我们需要添加的元素添加到对应size下标的位置,并且然后size自增1,代表现在已经有一个元素了,然后return true

到此我们得ArrayList调用add方法添加元素得源码就分析完了,我们总结一下:

  1. ArrayList数据结构是一个数组。
  2. ArrayList初始长度是10。
  3. ArrayList每次扩容都是原来的大小的一半,并且采用的是右移1的方式(长度 >> 1),因为计算性能比直接长度 / 2要好。
  4. ArrayList不是线程安全的,因为我们通过源码分析并没有发现任何像同步块或者是加锁的操作,如何想使用线程安全的ArrayList可以用CopyOnWriteArrayList,底层也是一个数组,但是比较骚的是,这个每次添加元素直接就扩容一个长度,然后add方法是采用ReentrantLock类方式进行加锁,后面另外开个帖子说下这个。

记住这三点面试别人问你ArrayList的都不虚,因为这是最简单的一个集合了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值