ArrayList源码分析总结

简单介绍

1、有下标,查询效率高
2、数组增删改方法涉及数组拷贝,效率低
3、方法未加锁及其他同步操作.线程不安全
4、使用频率很高,理解较为简单

创建

注意:它的初始化并不是在构造方法,而是在add方法里

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

1、无参构造

	/**
     * 共享的空数组实例,用于默认大小的空实例。
     *我们将其与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时需要多少空间
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 


	/**
     * Constructs an empty list with an initial capacity of ten.
     * 构造一个初始容量为10的空列表。(感觉这句注释并不准确。ArrayList在创建,未指定空间的话,默认是创	建一个空数组)
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

2、有参构造

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
    	this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) { 
        //传入值==0,则使用EMPTY_ELEMENTDATA,区分无参构造DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    	this.elementData = EMPTY_ELEMENTDATA;
    } else {
    	throw new IllegalArgumentException("Illegal Capacity: "+
    	initialCapacity);
    }
}

3、传入某种类型的元素列表

 //传入一个包含指定*集合元素的列表,然后迭代保证其元素的顺序
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
 }

添加

1、add(E e)

 //不带下标的添加,默认插入末尾
   //调用上面的公共方法
   public boolean add(E e) {
       ensureCapacityInternal(size + 1);  // Increments modCount!!
       elementData[size++] = e;
       return true;
   }

2、添加的公共方法类(扩容相关)

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

//得到最小扩容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //如果数组==空的数组,则比较默认容量与添加元素之和现在数组的长度+1,得较大的数返回
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);//扩容方法
}

//要分配的数组的最大大小。 一些虚拟机在数组中保留一些标题字。尝试分配更大的阵列可能会导致
//OutOfMemoryError:请求的阵列大小超出VM限制
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//ArrayList的扩容方法(重点!!!!)
//增加容量,以确保它至少可以容纳最小容量参数指定的*个元素。
//@param minCapacity所需的最小容量
private void grow(int minCapacity) {
    // overflow-conscious code
    //定义未添加数组的长度为旧值
    int oldCapacity = elementData.length;
    
    //定义旧值,jdk8版本之后位移运算1位后的值,也就是1.5倍的值为新值
    //oldCapacity >> 1 == oldCapacity除以2
    //注意区分下jdk的版本
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    //新容量是否小于最小容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
// 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法
//来比较 minCapacity 和 MAX_ARRAY_SIZE,
//如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,
//新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。
    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);
}

3、带下标的添加
public void add(int index, E element) {
//判断是否越界
rangeCheckForAdd(index);
//调用2的公共方法
ensureCapacityInternal(size + 1); // Increments modCount!!
//复制数组(也是arraylist不适合大规模数据增删改的原因)
//参数解析省略了,很简单
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
4、addAll(list)
首先将参数集合转为数组(list.toArray),然后和add(element)一样,然后源码使用了System.arraycopy进行数组拷贝。
5、addAll(index,list)
一样先判断索引是否越界,然后将参数集合转为数组,然后一样和add(element)一样,然后计算要移动的个数size-index,移动参数集合长度。如果需要移动的个数大于0,就使用System.arraycopy进行数组拷贝。移动完后再次调用System.arraycopy将参数数据添加进去。

删除

	//删除对应下标元素,后续元素左移。涉及了数组拷贝	
    public E remove(int index) {
        //检查了是否大于数组最大值
        rangeCheck(index);
        //这里没懂,后期理解,会补上
        modCount++;
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

Set(index,e)

一样校验索引是否越界,然后修改对应index的数据,返回旧的值。

Get(index)

一样校验索引是否越界,然后根据index,获取集合中的数据。

list里的toString方法

ArrayList继承AbstractList->继承AbstractCollection,
底层,先用迭代器判断有没有元素,没有就返回[],有就先用StringBuider拼接[,然后调用迭代器取值拼接,有元素拼接,空格,最后拼接],然后把整个缓冲区转为字符串。

迭代器遍历

Iterator先默认设置光标为0,然后判断光标是否等位集合大小,然后校验预期修改(expectedModCount)和实际修改(ModCount)是否相同,然后就会把光标+1,取出光标未+1时候的数组元素。
当remove的元素是倒数第二个的时候,使用list的remove不会出现报错异常,因为hashNext()下标=size,返回false
使用迭代器的remove的时候会(预期修改)expectedModCount=ModCount(实际修改)

Contains

底层就是一个一个遍历过去,找到相等的,没有找到就返回-1>0返回false,说明没找到。

isEmpty

根据size==0判断是否有元素。

总结

1.ArrayList频繁扩容导致添加性能急剧下降,如何处理?

通过构造方法指定初始容量的空列表,但是这样也会造成资源浪费。

2.ArrayList插入和删除元素一定比LinkedList慢么?不一定

ArrayList删除是数组的拷贝。
LinkedList是遍历链表从头或者尾巴开始,然后再删除节点。

3、ArrayList是线程不安全的(private成员变量会不安全、局部变量安全)。因为可能同时加到同一个位置。

1、加同步代码块,synchronized(this){}
2、lock锁
3 、同步方法synchronized
4.使用new Vector()类 add方法里有(synchronized)
5.推荐:Collections的synchronized()方法
6.读和写都安全的集合CopyOnwriteArrayList底层add采用的是(ReentrantLock)(读写分离,读和写不同的容器)
类似的还有CopyOnwriteArraySet它new CopyOnwriteArrayList

4、ArrayList和LinkList的区别

1.ArrayList是数组 LinkList是链表
2.随机访问get与set方面比ArrayList优于LinkList。
3.新增和删除操作,LinedList比较占优势
4.LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

5、ArrayList扩容过程?

就是先通过最小容量大于数组长度进入grow方法
1.得到当前的ArrayList的容量(oldCapacity)。
2.计算除扩容后的新容量(newCapacity),其值(oldCapacity + (oldCapacity >> 1))约是oldCapacity 的1.5倍。
3.当newCapacity小于所需最小容量,那么将所需最小容量赋值给newCapacity。
4.newCapacity大于ArrayList的所允许的最大容量,处理。
5.进行数据的复制,完成向ArrayList实例添加元素操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值