ArrayList集合简单源码分析+一道面试题

ArrayList类中的属性

java 复制代码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    @java.io.Serial
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * DEFAULT_CAPACITY表示集合默认的初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组EMPTY_ELEMENTDATA,在使用有参构造方法时传递0进去,创建ArrayList集合时就会使用这个数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
        默认容量的空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 集合中真实存储元素的数组elementData
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * 集合中元素的个数
     * @serial
     */
    private int size;    //int类型的成员变量初始值为0

ArrayList类中的构造方法

  • 无参构造方法:相当于内部创建了一个空的数组
java 复制代码
    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        //将成员变量中的 默认容量的空数组 赋值给 真实存储元素的数组
        //this.elementData = {}
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • 有参构造方法分析:内部创建了一个指定长度的数组 并赋值给了elementData

main方法:

java 复制代码
   
public class Test {
    public static void main(String[] args) {
        ArrayList list = new ArrayList(20);  //创建一个指定初始容量为20的ArrayList集合
    }
}
  • 有参构造方法源码:
java 复制代码
    public ArrayList(int initialCapacity) {   // initialCapacity=20
        if (initialCapacity > 0) {
            //内部创建了一个长度为20的数组,并赋值给this.elementData
            this.elementData = new Object[initialCapacity]; 
        } else if (initialCapacity == 0) {
            //如果传递的是0,就将空数组EMPTY_ELEMENTDATA 赋值给 this.elementData
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //如果在构造方法中传递负数,就会抛出如下异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

无参构造方法创建的对象:添加方法

第一次添加数据:

在main方法中:

java 复制代码
import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        list1.add(1);   //添加第一个元素进去
    }
}

第一次添加数据的流程:

  • 一级一级的调用下去
java 复制代码
    public boolean add(E e) {
        //确定内部数组的容量          0 + 1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将要添加的元素 添加 到数组中有数据的下一个位置
        elementData[size++] = e;        
        return true;
    }

-------------------------------------------------------------------------------------
    //minCapacity:是ArrayList集合需要的最小容量(暂时这样理解)
    private void ensureCapacityInternal(int minCapacity) { // minCapacity=1
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {   // {}=={}
            // 						10					1
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
 
        //通过得到的minCapacity去确定数组的长度
        ensureExplicitCapacity(minCapacity);
    }

-------------------------------------------------------------------------------------
    private void ensureExplicitCapacity(int minCapacity) {  //minCapacity=10
        modCount++;   //记录对当前集合操作的次数
 
        // 集合需要的最小容量minCapacity 大于 elementData数组的长度时就会扩容(也就是当集合内部的数组不够存储元素时才会扩容)    10-0
        if (minCapacity - elementData.length > 0)
            //扩容
            grow(minCapacity);
    }

-------------------------------------------------------------------------------------
	private void grow(int minCapacity) {    //minCapacity=10
		int oldCapacity = this.elementData.length;   //原来的数组长度为0
		int newCapacity = oldCapacity + (oldCapacity >> 1);    //新的容量0
        
		if (newCapacity - minCapacity < 0) {   //0 - 10 < 0,执行
			newCapacity = minCapacity;     //newCapacity = 10 
		}
 
		if (newCapacity - 2147483639 > 0) {  //10 - 21亿 < 0
			newCapacity = hugeCapacity(minCapacity);
		}
 
        //将空数组{} 复制到 一个长度为 10的新数组中,然后将新的数组赋值给elementData。此时size还是0
		this.elementData = Arrays.copyOf(this.elementData, newCapacity);
	}

	//然后回去继续向下执行,执行的就是向数组中添加数据的操作
    public boolean add(E e) {
        //确定内部数组的容量        0 + 1
        ensureCapacityInternal(size + 1);  // 0 + 1
        //将要添加的元素 添加 到数组中有数据的下一个位置
        elementData[size++] = e;        //elementData={e,,,,,,,,,} 数组的length=10,size=1
        return true;
    }
第二次添加元素:
  • 第二次添加元素也还不会扩容

main方法中:

java 复制代码
package string.demo;

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        list1.add(1);
        list1.add(2);  //第二次添加数据
    }
}

第二次添加元素的执行流程:

java 复制代码
    public boolean add(E e) {
        //确定内部数组的容量 1 + 1 = 2
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将要添加的元素 添加 到数组中有数据的下一个位置
        elementData[size++] = e;        
        return true;
    }

-------------------------------------------------------------------------------------
    private void ensureCapacityInternal(int minCapacity) { // minCapacity=2
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {   // {1,,,...}!={} 不执行语句体
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
 
        //确定数组的容量
        ensureExplicitCapacity(minCapacity);
    }

-------------------------------------------------------------------------------------
    private void ensureExplicitCapacity(int minCapacity) {  minCapacity=2
        modCount++;   //记录对当前集合操作的次数 modCount=modCount + 1   (modCount=2)
 
        // 集合需要的最小容量minCapacity 大于 elementData数组的长度时就会扩容    2 - 10 < 0:不执行if语句体,也就是不扩容
        if (minCapacity - elementData.length > 0)
            //扩容
            grow(minCapacity);
    }
-----------------------------------------------------------------------------------------
    //然后回去继续向下执行,执行的就是向数组中添加数据的操作
    public boolean add(E e) {
        //确定内部的容量 0 + 1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将要添加的元素 添加 到数组中有数据的下一个位置
        elementData[size++] = e;     //  {1,2,.....}  此时:数组length = 10,元素个size=2
        return true;
    }
第十一次添加元素
  • 此时ArrayList集合内部的数组不够存储新元素了,会扩容
java 复制代码
    public boolean add(E e) {
        //此时elementData数组:{1,2,3,4,5,6,7,8,9,10}   size = 10 , length = 10
        ensureCapacityInternal(size + 1);  // 10 + 1 =11
        //将要添加的元素 添加 到数组中有数据的下一个位置
        elementData[size++] = e;    
        return true;
    }

-------------------------------------------------------------------------------------
    private void ensureCapacityInternal(int minCapacity) {  minCapacity = 11
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {   
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
 
        //确定数组的容量
        ensureExplicitCapacity(minCapacity);  //11
    }

-------------------------------------------------------------------------------------
    private void ensureExplicitCapacity(int minCapacity) {  minCapacity=11
        modCount++;   //记录对当前集合操作的次数 modCount=modCount + 1   (modCount=11)
 
        // minCapacity 大于 elementData数组的长度时就会扩容    11-10 > 0:扩容
        if (minCapacity - elementData.length > 0)
            //扩容
            grow(minCapacity);
    }

-------------------------------------------------------------------------------------
	private void grow(int minCapacity) {    //minCapacity=11
    	//oldCapacity = 10
		int oldCapacity = this.elementData.length;  //获取原容量的长度
    
    	//新的容量newCapacity是在原容量的基础上扩容1.5倍
    	//newCapacity = 10 + 5
		int newCapacity = oldCapacity + (oldCapacity >> 1);    
        
		if (newCapacity - minCapacity < 0) {   //15 - 11 > 0,不执行
			newCapacity = minCapacity;     
		}
 
		if (newCapacity - 2147483639 > 0) {  //15 - 21亿 < 0,不执行
			newCapacity = hugeCapacity(minCapacity);
		}
 
        //将{1,2,3,4,5,6,7,8,9,10}长度为10的数组 复制到 长度为 15的新数组中,然后将新的数组赋值给elementData。
		this.elementData = Arrays.copyOf(this.elementData, newCapacity);
	}

	//再回去执行向数组中添加元素的逻辑
    public boolean add(E e) {
        //此时elementData数组:{1,2,3,4,5,6,7,8,9,10}   size = 10 , length = 10
        ensureCapacityInternal(size + 1);  // 10 + 1 =11
        //将要添加的元素 添加 到数组中有数据的下一个位置{1,2,3,4,5,6,7,8,9,10,11,,,,} 此时size=11 length=15
        
        elementData[size++] = e;    
        return true;
    }

有参构造方法创建的对象:添加方法

执行时调用的方法都和无参的一样

main方法:

java 复制代码
package string.demo;

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        //elementData = {,,,,,} 长度为20的数组
        ArrayList list2 = new ArrayList(20);
        list2.add(1);
    }
}

添加数据:

java 复制代码
    public boolean add(E e) {
        //确定内部的容量 0 + 1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将要添加的元素 添加 到数组中有数据的下一个位置
        elementData[size++] = e;        
        return true;
    }

-------------------------------------------------------------------------------------
    
    private void ensureCapacityInternal(int minCapacity) { // minCapacity=1
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {   // elementData != {}不执行
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
 
        //确定数组的容量
        ensureExplicitCapacity(minCapacity);
    }

-------------------------------------------------------------------------------------
    private void ensureExplicitCapacity(int minCapacity) {  //minCapacity=1
        modCount++;   //记录对当前集合操作的次数
 
        // 集合中的元素个数 等于数组的长度时才会扩容   
        if (minCapacity - elementData.length > 0)  //1 - 20 < 0 不扩容
            //扩容
            grow(minCapacity);
    }

-------------------------------------------------------------------------------------
	private void grow(int minCapacity) {      
		int oldCapacity = this.elementData.length;   
		int newCapacity = oldCapacity + (oldCapacity >> 1);    //新的容量
        
		if (newCapacity - minCapacity < 0) {   
			newCapacity = minCapacity;     
		}
 
		if (newCapacity - 2147483639 > 0) {  
			newCapacity = hugeCapacity(minCapacity);
		}
 
		this.elementData = Arrays.copyOf(this.elementData, newCapacity);
	}

ArrayList中的get方法

main方法:

java 复制代码
package string.demo;

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        list1.add(1);
        list1.add(2);
        list1.get(0);
    }
}

ArrayList中的get方法源码:

java 复制代码
    public E get(int index) {
        //检查下标是否合法
        Objects.checkIndex(index, size);
        //通过下标获取数组中对应的元素
        return elementData(index);
    }

ArrayList中的set方法

main方法:

java 复制代码
package string.demo;

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        list1.add(1);
        list1.add(2);
        list1.set(1,"aaa");
    }
}

ArrayList中的set方法源码:

jdk17:

java 复制代码
    public E set(int index, E element) {
        Objects.checkIndex(index, size); //检查下标是否合法
        E oldValue = elementData(index);  //获取原来下标对应的元素
        elementData[index] = element;   //将新的元素赋值给数组中对应的下标位置上
        return oldValue;   //返回原来的值
    }

过去版本的jdk:

java 复制代码
    public E set(int index, E element) {
        rangeCheck(index);  //检查下标是否合法
 
        E oldValue = elementData(index);  //获取原来下标对应的元素
        elementData[index] = element;   //将新的元素赋值给数组中对应的下标位置上
        return oldValue;   //返回原来的值
    }

ArrayList中的remove方法:

main方法:

java 复制代码
package string.demo;

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        list1.add(1);
        list1.add(2);
        list1.add(3);
        list1.add(4);
        list1.add(5);
        list1.add(6);
        list1.add(7);
        list1.add(8);
        list1.add(9);
        list1.remove(3);
    }
}

ArrayList中的remove方法源码:

过去版本的jdk:

java 复制代码
    public E remove(int index) {     index=3
        rangeCheck(index);    //检查下标
 
        modCount++;  //记录修改当前集合的次数
        E oldValue = elementData(index);  //获取原来下标上的元素
 
        //计算要移动的元素的个数{1,2,3,4,5,6,7,8,9} 
                             //{1,2,3,5,6,7,8,9,null}
        int numMoved = size - index - 1;    //9 - 3 - 1 = 5
        if (numMoved > 0)
            System.arraycopy(elementData   //原数组
                             , index+1   //开始移动的下标 5,6,7,8,9
                             , elementData   //目标数组
                             , index,   //开始的下标
                             numMoved   //要移动的元素个数
                            );
                                
        //{1,2,3,5,6,7,8,9,null}                        
        elementData[--size] = null; // clear to let GC do its work
 
        return oldValue;
    }
java 复制代码
    public E remove(int index) {
        Objects.checkIndex(index, size);
        final Object[] es = elementData;

        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);

        return oldValue;
    }

TreeSet的实现原理

在构造方法中创建了一个TreeMap实例

java 复制代码
    public TreeSet() {
        this(new TreeMap<>());
    }

在调用add方法时,本质上是调用了TreeMap的put方法

java 复制代码
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

Hashset的实现原理

在其无参构造方法中实例化了一个HashMap实例

java 复制代码
    public HashSet() {
        map = new HashMap<>();
    }

在调用add方法的时候本质上是调用了HashMap的put方法

java 复制代码
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

去除ArrayList集合中重复的元素

方式一:

会创建一个新的ArrayList集合

java 复制代码
package collection;

import java.util.ArrayList;

public class ListTest {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        list1.add(1);
        list1.add(3);
        list1.add(2);
        list1.add(null);
        list1.add("张三");
        list1.add("李四");
        list1.add(3);
        System.out.println(list1);
        //新创建一个List集合存储去重后的元素
        ArrayList list2 = new ArrayList();
        for (Object o : list1){   //遍历原来的集合
            if (!list2.contains(o)){   //判断新的集合中是否包含list1中的元素,如果没有则1添加到list2中
                list2.add(o);
            }
        }
        System.out.println(list2);

    }
}

运行结果:

[1, 3, 2, null, 张三, 李四, 3]

[1, 3, 2, null, 张三, 李四]

方式二

不创建新集合,使用选择排序的思想去除重复元素

java 复制代码
package collection;

import java.util.ArrayList;

public class ListTest {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        list1.add(1);
        list1.add(3);
        list1.add(2);
        list1.add(2);
        list1.add(2);
        list1.add("张三");
        list1.add("李四");
        list1.add(3);
        System.out.println(list1);

        for (int i = 0;i < list1.size() - 1;i++){
            for (int j = i + 1;j<list1.size();j++){
                if (list1.get(i).equals(list1.get(j))){
                    list1.remove(j);
                    j--;
                }
            }
        }
        System.out.println(list1);
    }
}
相关推荐
Algorithm15767 分钟前
云原生相关的 Go 语言工程师技术路线(含博客网址导航)
开发语言·云原生·golang
shinelord明17 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
新手小袁_J22 分钟前
JDK11下载安装和配置超详细过程
java·spring cloud·jdk·maven·mybatis·jdk11
呆呆小雅23 分钟前
C#关键字volatile
java·redis·c#
Monly2123 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
boligongzhu24 分钟前
DALSA工业相机SDK二次开发(图像采集及保存)C#版
开发语言·c#·dalsa
Eric.Lee202124 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
Ttang2326 分钟前
Tomcat原理(6)——tomcat完整实现
java·tomcat
7yewh27 分钟前
嵌入式Linux QT+OpenCV基于人脸识别的考勤系统 项目
linux·开发语言·arm开发·驱动开发·qt·opencv·嵌入式linux
钱多多_qdd36 分钟前
spring cache源码解析(四)——从@EnableCaching开始来阅读源码
java·spring boot·spring