ArrayList

第一次写了这么多的文字,也是帮助自己记录一下学习的过程。如果有什么错误的地方请各位指出,谢谢!

最近准备跳槽(哈哈),顺手记录一下ArrayList。

重要的成员变量:

//默认初始容量为10
private static final int DEFAULT_CAPACITY = 10;
//用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认大小的空数组实例。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList 的元素存储在其中的数组缓冲区。 
transient Object[] elementData; 
//它实际包含的元素的个数
private int size;
//ArrayList的最长的长度,因为有些VM会在数组中保留一些头字所以预留了
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA区别:

调用ArrayList的构造时,无参构造elementData =DEFAULTCAPACITY_EMPTY_ELEMENTDATA,有参构造传0时,elementData = EMPTY_ELEMENTDATA。DEFAULTCAPACITY_EMPTY_ELEMENTDATA第一次add扩容时为10,EMPTY_ELEMENTDATA第一次add为1。这两个是用来共享给空数组的,避免了创建了很多ArrayList,而每个ArrayList中都有空数组的问题,提高了性能。

构造方法:

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

构造方法解析:

  • 无参构造:会将默认容量的共享空数组的引用赋值给elementData,add时,判断elementData的引用是否是默认容量共享空数组,如果是,就会将elementData扩展为长度为默认容量(10)的数组,然后才添加元素。
  • 有参构造:
    • initialCapacity大于0:直接新建一个长度为initialCapacity的数组,并将引用赋值给elementData,这里就相当于直接进行了扩展了。初始化完成elementData的长度就已经是initialCapacity了,直到add到超出了该长度时才会进行扩展。
    • initialCapacity等于0:因为initialCapacity等于0,所以elementData的数组应该为空数组,这里就直接将EMPTY_ELEMENTDATA的引用赋值给elementData,扩展时也会根据实际需要的大小进行在扩展。

方法

add:add方法分为确保容量、赋值、增加实际元素数量(size)

  1. 确保容量:添加元素前必须确保elementData中有足够的空间来存储元素。首先得到添加元素需要的最小容量=size+1,然后判断elementData是否是默认容量的空数组,如果是那么说明elementData应该再第一次扩展时将长度扩展为默认容量(10),然后取默认容量和需要的最小容量的较大值作为elementData应该有的长度,然后这个长度和elementData的实际长度进行比较,如果这个长度大于实际长度就会对elementData进行扩展。
  2. 将e(需要添加的元素)添加到size+1的位置上面。

扩容机制:

从下面这行代码可以看出容量计算方式为:原容量 + 原容量/2
所以如果第一次为0,则计算得出1,
为1,则计算得出2,
为2,则计算得出3,
为3,则计算得出4,
为4,则计算得出6,

为10,则计算得出15,
为15,则计算得出22

int newCapacity = oldCapacity + (oldCapacity >> 1);
//将指定的元素添加到此列表的末尾
public boolean add(E e) {
    ensureCapacityInternal(size + 1); 
    elementData[size++] = e;
    return true;
}

//在指定下标插入元素
public void add(int index, E element) {
    //index不能大于elementData中实际的数据的数量,也不能小于0
    rangeCheckForAdd(index);
    //确保内部容量,如果需要会进行扩容
    ensureCapacityInternal(size + 1);  
    //对elementData进行复制,因为数组的length一定大于size,所以不会
    //出现下标越界
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //进行赋值,并且size+1
    elementData[index] = element;
    size++;
}


//以下两个方法的步骤:1.确保内部容量 2.数组复制 3.增加size

//在指定下标依次插入c的所有元素
public boolean addAll(int index, Collection<? extends E> c)
//在后面依次插入c的所有元素
public boolean addAll(Collection<? extends E> c)

其他方法:

//清空所有。循环遍历size次,将所有下标对应的元素设置为null,并将size设为0,并不会修改elementData的长度
public void clear() {
    for (int i = 0; i < size; i++)
        elementData[i] = null;
    size = 0;
}
//返回此ArrayList实例的浅表副本。 浅拷贝,拷贝对象引用
public Object clone()

//如果此列表包含指定的元素,则返回true 。调用indexOf方法。大于0说明存在指定的元素
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

//返回指定元素在arrayList中的下标,如果o为null就返回第一个null的下标,如果不为null,则返回第一个equals相等的元素的下标。都没有返回-1
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

//forEach方法:遍历。
arrayList.forEach( it -> {
    System.out.println("it = " + it);
});

//获取指定下标的元素
public E get(int index) {
    //检验下标,不能大于等于size
    rangeCheck(index);
    //elementData(index)是取出(E) elementData[index]返回
    return elementData(index);
}
//返回elementData的元素个数是否为0
public boolean isEmpty() {
    return size == 0;
}
//和indexOf一样只是遍历的时候从size开始遍历
public int lastIndexOf(Object o)

//删除指定下标的元素
public E remove(int index) {
    rangeCheck(index);//检查不能大于等于size
    E oldValue = elementData(index);//先取出需要删除的元素
    //将index后面的元素向前移动一个位置
    int numMoved = size - index - 1;
    if (numMoved > 0){//如果需要移动的位置大于0就复制数组
       System.arraycopy(elementData, index+1, 
       elementData, index,numMoved); 
    }
    // 将原来的最后一个元素的下标对应设置为null,并size--
    elementData[--size] = null; 
    return oldValue;//返回删除了的元素
}
//正向遍历,如果equals相等就删除第一个
public boolean remove(Object o)

//设置指定下标的元素为element
public E set(int index, E element)={elementData[index] = element}

size();//获取elementData中的元素的总个数

//排序,返回正数升序,负数降序
sort((o1, o2) -> {
    return o1-o2;
});

常见面试题

ArrayList是什么

ArrayList就是有序的数组列表,不能存放基本类型,它的主要底层实现是数组。

ArrayList与LinkedList的区别?

1、ArrayList的查找和访问元素的速度较快,但新增,删除的速度较慢,因为需要确保容量足够,所以有可能需要扩容有,操作还会移动元素位置,所以速度较慢。LinkedList的查找和访问元素的速度较慢,但是他的新增,删除的速度较快,因为是使用链表的形式。
2、ArrayList需要分配一块连续的内存空间,LinkedList不需要连续的内存空间
3、两个线程都不安全

ArrayList(int initialCapacity)会不会初始化数组⼤⼩?

当initialCapacity大于0时会直接初始化elementData的大小
当initialCapacity等于0时是使用的共享空数组,避免存在很多的空数组
当调用无参构造时,elementData使用的是默认容量的共享空数组。
两个空数组的区别在于:默认容量的共享空数组第一次扩容时elementData的长度为10,而共享空数组第一次扩容时根据 原长度 + 原长度/2 计算出的长度进行扩展。

ArrayList的遍历为什么快?

因为底层是数组,数组的内存空间是连续的,CPU读取时会缓存那一块内存片段,而数据在内存中都是连续的,所以遍历快。

ArrayList容量的增加机制

如果使用的无参构造创建,第一次add时扩展长度为10(默认容量)。如果有参构造传递的初始容量大于0,就会直接给elementData分配空间,创建这个初始容量长度的数组。如果等于0,那么在add扩展时就会从0开始计算需要扩展的新的容量。当有参构造参数为0或者其他方式第二次扩展时,得到的新的容量 = 旧的容量 + 旧的容量/2。ArrayList类中说了了MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8,vm可能会保留一些头字,所以将最大的长度设置为此值。如果最小需要的容量大于了MAX_ARRAY_SIZE则新的容量为Integer.MAX_VALUE(可能会导致 OutOfMemoryError),否则新的容量就是MAX_ARRAY_SIZE。

ArrayList的大小是如何自动增加的?

在进行add操作时会先检查elementData的容量来判断是否需要进行扩展以加入这个数据。如果容量不足,就会新建一个容量更大的数组。然后将原数组的数据拷贝到新数组中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值