ArrayList源码分析

ArrayList源码分析

本文章,纯手写,如果发现有什么问题或者不懂的都可以联系我哦!!!
同时谢谢你们的支持和鼓励~~

ArrayList类的继承和接口

首先看一下ArrayList这个类的继承和实现关系

在这里插入图片描述

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

从上面可以看出它继承了AbstractList,实现了RandomAccess, Cloneable, java.io.Serializable这几个接口

那么来分析一下这几个的作用吧

  1. AbstractList

    通过看这个抽象类的源码可以知道,这个就是ArrayList的骨架,所以ArrayList会通过AbstractList来构建自己的骨架和方法

  2. RandomAccess

    这个接口提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。在实践中,如果集合实现了这个接口,那么就建议采取随机访问,这样速度会更快,如果没有实现这个接口,建议采取顺序访问;因为有无这个接口然后选择访问的方式会对访问速度有很大的影响。

  3. Cloneable
    这个接口主要是提供了ArrayList可以被拷贝的功能

  4. java.io.Serializable

    这个接口主要提供了ArrayList的序列化功能,因为实现了序列化,所以在实践中,如果有大量数据需要进行存储或者取出,那么就可以考虑使用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; // non-private to simplify nested class access
//ArrayList中元素所占数组的大小
private int size;

然后从它的三个构造方法说起:

    ArrayList list1 = new ArrayList<>();
    ArrayList list2 = new ArrayList<>(12);
    ArrayList list3= new ArrayList<>(list1);
  1. 第一个无参构造

    进行debug来看看怎么初始化的

    //可以看到,无参构造初始化,进入的就是他自身的无参构造器
    public ArrayList() {
        //这是将本身的数组设置为空数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
  2. 第二个有参构造

    //有参构造就会进入到这个构造方法中
    //initialCapacity这个就是传过来的初始化容量参数
    public ArrayList(int initialCapacity) {
        //判断如果初始化容量的参数大于0,那么就将新创建的数组(这个数组的容量就是前面传过来的初始化容量大小)赋给本地的数组
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //如果初始化容量大小等于零,那么就将本地数组置为空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //如果上面两种条件都不满足,那么就会报出非法容量异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    
  3. 第三个有参构造

    //进入到这个构造方法中,参数传递是集合
    public ArrayList(Collection<? extends E> c) {
        //首先将传过来的集合变成数组,然后将这个数组赋给本地数组
        elementData = c.toArray();//有关这个方法的调用在下面代码中指出
        //这里将size赋值,然后判断本地数组的长度	是否等于0
        if ((size = elementData.length) != 0) {
            // 如果不等于0,再进行判断本地数组的类和对象数组的类是否相同
            if (elementData.getClass() != Object[].class)
                //如果不相同,那么就会将本地数组的类型转换成对象数组类型
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 如果上述条件都不符合,那么就会将本地数组置为空
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    
    
    //这是上面的toArray方法
    public Object[] toArray() {
        //而这个方法的底层实现是将数据拷贝到一个新数组,然后将这个新数组进行返回
        return Arrays.copyOf(elementData, size);
    }
    
    //original就是传过来的数据,newLength就是传过来的集合长度
    public static <T> T[] copyOf(T[] original, int newLength) {
            return (T[]) copyOf(original, newLength, original.getClass());
        }
    
    //实际上调用的就是这个方法!!!!!
    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);
        //这里是进行数据拷贝,Math.min(original.length, newLength)这个是比较传过来的数据长度和传过来的长度大小。
            System.arraycopy(original, 0, copy, 0,
                             Math.min(original.length, newLength));
            return copy;
        }
    

    通过上面的构造方法,可以知道ArrayList是如何初始化的

ArrayList是怎么实现增删改查的?

下面再通过它的增删改查来进一步探索

从增加开始说起,直接上代码

//就从这个代码开始debug吧
ArrayList list1 = new ArrayList<>();
list1.add("aaa");
//首先进入的是这个添加方法中,这是boolean类型的方法,但是它返回的永远是ture
public boolean add(E e) {
    //这个方法主要是判断是否是第一次添加,然后数组是否初始化过,然后再进行后面的扩容判断。
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    
    //在将元素添加进数组之前,需要判断这个数组的容量是否能够让这个元素添加进去
    
    //将size作为下标,存储传进来的元素
    elementData[size++] = e;
    return true;
}

 private void ensureCapacityInternal(int minCapacity) {
     //因为是第一次添加操作,所以传过来的minCapacity最小值是1,也就是前面的size + 1
     //这里进行判断,本地数组是否为空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //如果是空数组,那么就将这个最小容量设置为默认容量,也就是10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

     //进行扩容判断
        ensureExplicitCapacity(minCapacity);
    }


 private void ensureExplicitCapacity(int minCapacity) {
     //计数用的
        modCount++;

        
     //判断预扩展的值与当前数组的容量大小
        if (minCapacity - elementData.length > 0)
            //如果大于,说明数组容量不够,会进行扩容操作
            grow(minCapacity);
    }

private void grow(int minCapacity) {
        // oldCapacity将本地数组长度记录下来
        int oldCapacity = elementData.length;
    
    	//>>是右移,也就是除以2的几次幂
    	//<<是左移,也就是乘以2的几次幂
    
    	//newCapacity=旧的容量+将旧的容量右移后的结果     
    	//得到的结果就是newCapacity是原来旧容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
    	//判断如果新的容量减去最小容量小于0
        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);
    }


 private static int hugeCapacity(int minCapacity) {
     //如果最小容量小于0,内存溢出异常
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
     //如果上述条件不满足,进行三元运算,如果最小容量大于最大数组容量,那么就会将这个更大的值返回,否则就返回最大数组容量
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

将思路捋清:这个代码主要是在添加元素之前将确保一下数组容量的大小,以便后面可以正常的元素插入。在这个确保容量的时候,首先如果是第一次添加进来的,那么因为我们初始化集合是用的无参构造方法,所以先将这个数组的大小设置为默认大小。设置完之后,再判断添加的元素之后的长度是否大于当前数组的容量,如果大于那么就会进入到扩容中,然后将旧的容量扩充为原来的1.5倍(**这里主要是针对第一次进来时的情况:**如果扩充的容量比最小容量还小,那么就标志这扩容失败,容量还是采用最小容量,如果容量大于最大数组容量那么就会再进行相关的判断…)

思考思考思考!!!!!

到了这里,其实我还是有很多的疑问,虽然源码看明白了,但是为什么是这么个流程呢?

带着问题,将源码更通透的理解一下:

为什么是否扩容的判断条件如此难懂?
minCapacity - elementData.length > 0 ????

为什么最小容量大于元素存储的容量还要进行扩容,这里最小容量大于存储容量,那就证明有空间存储啊,为啥还要进行扩容,反而最小容量如果小于那就什么操作也不做????
下面纯属个人理解

首先这里有一个错误的理解受第一次添加数据的影响,我把minCapacity当成了一个定值,我一直把这个值当做是10来处理,其实这个最小容量是根据每次添加数据动态更新的值;elementData.length我把它理解成当前数组中存储元素的长度,其实是初始化数组长度的值!!!因此,我无法理解这个扩容的判断条件。

正确思路:将上述的错误思想改正之后,豁然开朗,解决思路就是我来了一个一万次循环将元素添加进集合当中,那么这个时候,你会发现这个minCapacity是个什么东西呢,它其实就是在我们添加元素之前,将数组容量预扩展一个存放元素的空间,然后用这个预扩展的值减去当前数组的长度,如果这个长度大于数组长度,那么就说明数组长度不够,这时候就会调用grow方法,将数组扩容为原来的1.5倍。

如果上面的描述还是不懂,那么现在假设现在循环到了第十一次,到调用这个添加方法的时候,首先判断容量是否够,传过去的参数就是minCapacity=11,而这个时候elementData.length就是当时初始化的长度,也就是10,所以说现在是不是就必须得将数组扩容呢?那么就进入到grow方法当中,一顿操作下来,那容量不就变成了15嘛,然后不就可以继续存元素了吗???

经过上面的一顿分析之后,下面再看看另外的几个添加方法:

public void add(int index, E element) 在指定位置添加元素

ArrayList list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
//添加到指定位置
list1.add(1,"abc");
//进入到这个方法当中
public void add(int index, E element) {
    //校验传过来的下标是否合格
    rangeCheckForAdd(index);
	//校验数组容量是否设置过,然后判断是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length);
    //src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,destPos在目标数组中开始赋值的位置,length表示要复制的长度。
    
    //主要就是这段copy代码,这段代码是怎么实现的呢???
    //首先数据源是elementData{aaa,bbb,ccc},也就是原数组,要复制的起始位置就是index=1(bbb),目标数组还是elementData{aaa,bbb,ccc},开始赋值的位置就是2(ccc)开始,要复制的长度为3-1=2;那么根据上述条件,我们知道复制完之后数组的数据变成了elementData{aaa,bbb,bbb,ccc}
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //然后将索引的位置变成要添加的元素值(在指定的地方添加元素)
    elementData[index] = element;
    size++;
}


private void rangeCheckForAdd(int index) {
    //是否大于数组长度,是否小于0,如果是就会抛出索引越界异常
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

public boolean addAll(Collection<? extends E> c) 添加集合元素

ArrayList list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
ArrayList list3= new ArrayList<>(list1);
list3.addAll(list1);
//进入到这个方法中来
public boolean addAll(Collection<? extends E> c) {
    //首先将集合变成一个对象数组
    Object[] a = c.toArray();
    //记录数组的长度
    int numNew = a.length;
    //校验数组的容量,判断是否扩容
    ensureCapacityInternal(size + numNew);  // Increments modCount
    //又来到的熟悉的地方,同样的,我们根据上一个方法来进行分析
    //数据源:a{aaa,bbb,ccc}  复制起始位置0  目标数组elementData{}  目标数组的位置0  复制的长度3
    //那么最终得到的结果就是复制过来的数组elementData{aaa,bbb,ccc}
    System.arraycopy(a, 0, elementData, size, numNew);
    //数组的长度变成原本的长度加上新添加数据的长度
    size += numNew;
    return numNew != 0;
}

public boolean addAll(int index, Collection<? extends E> c) 将集合添加到指定位置上

ArrayList list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
ArrayList list3= new ArrayList<>();
list3.add("ddd");
list3.add("eee");
list3.addAll(1,list1);
//进入到这个方法中
public boolean addAll(int index, Collection<? extends E> c) {
    //判断下标是否越界
    rangeCheckForAdd(index);
	//将集合先变成对象数组
    Object[] a = c.toArray();
    //将数组的长度取出
    int numNew = a.length;
    //校验是否有初始容量,以及是否需要扩容
    ensureCapacityInternal(size + numNew);  // Increments modCount

    //记录要移动的步数
    int numMoved = size - index;
    //如果大于0
    if (numMoved > 0)
        //首先是进行第一次拷贝,这次拷贝主要是将目标数组扩充为可以容纳新数组的数组
        //数据源elementData{ddd,eee}  复制的起始位置1  目标数组elementData  在目标数组赋值的位置1+3  复制的长度1    复制完成之后的数组变成elementData{ddd,eee,null,null,eee}
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);
	//第二次拷贝才是将真正的数据添加进数组中
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

修改方法set

上代码

ArrayList list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.set(1,"ddd");
public E set(int index, E element) {
    //判断索引下标是否越界
    rangeCheck(index);
	//将旧的值取出
    E oldValue = elementData(index);
    //将新的值修改
    elementData[index] = element;
    //返回旧的值
    return oldValue;
}

获取方法get

ArrayList list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.get(2);
public E get(int index) {
    //判断索引下标是否越界
    rangeCheck(index);
	//返回当前索引的值
    return elementData(index);
}

toString方法源码解析

ArrayList list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.toString();
//首先得明确一点,这个方法不是ArrayList的而是AbstractList中的方法
public String toString() {
    //主要使用的迭代器
    Iterator<E> it = iterator();
    //判断如果为空,就直接返回空串
    if (! it.hasNext())
        return "[]";

    //如果不为空,那么用StringBuilder进行字符串拼接
    StringBuilder sb = new StringBuilder();
    //先将结构搭建好
    sb.append('[');
    //然后用循环将里面的数据一一拼接到字符串上
    for (;;) {
        E e = it.next();
        //这里通过三元运算,判断y
        sb.append(e == this ? "(this Collection)" : e);
        if (! it.hasNext())
            //如果没有数据了,就进行结尾
            return sb.append(']').toString();
        sb.append(',').append(' ');
    }
}

迭代器的源码

这里我会通过三个不同的案例,来分析一下迭代器具体的源码,然后引申出相关的问题

第一个例子:通过迭代器遍历,使用集合自带的方法删除目标元素

ArrayList list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
Iterator it = list1.iterator();
while(it.hasNext()){
    String s = (String) it.next();
    if (s.equals("aaa")){
        list1.remove(s);
    }
}

将这个代码运行之后,你会发现代码报错

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at jihe.test.main(test.java:17)

为什么会报错呢???带着问题,查看源码!!!

//先进入这个迭代器的方法中,而后会发现,一个内部类继承iterator重写了迭代器方法
public Iterator<E> iterator() {
    return new Itr();
}


//主要来看一下这个类的具体实现
private class Itr implements Iterator<E> {
    int cursor;       // 这里是光标位置
    int lastRet = -1; // 返回最后一个元素的索引
    //将实际修改次数赋值给预期修改次数(报错的关键语句)
    int expectedModCount = modCount;

    //判断光标位置是否到达末尾
    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        //检查实际修改次数和预期修改次数是否一样,如果不一样那么就会报出刚刚的错误ConcurrentModificationException(报错的关键方法)
        checkForComodification();
        //将光标的值赋值给i
        int i = cursor;
        //size:就是数组的长度
        //然后用i和size进行比较,如果光标的值大于size的值,那么就会抛出异常NoSuchElementException
        if (i >= size)
            throw new NoSuchElementException();
        //将集合数组赋值给elementData
        Object[] elementData = ArrayList.this.elementData;
        //如果光标的值大于数组的长度抛出异常ConcurrentModificationException
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        
        //光标自增
        cursor = i + 1;
        //将i的值赋值给lastRet,并且将当前遍历到的数据进行返回
        return (E) elementData[lastRet = i];
    }


    
final void checkForComodification() {
    //判断实际修改次数和语气修改次数是否相等
        if (modCount != expectedModCount)
            //不想等就直接报出ConcurrentModificationException异常
            throw new ConcurrentModificationException();
    }
    
    
    
//上面的遍历大致就是那么个思路,然后根据程序的执行,我们会进行比较判断,如果当前遍历的值与要删除的值相同,那么就调用ArrayList自带的移除方法
public boolean remove(Object o) {
    //首先判断传过来的这个值是否为空
    if (o == null) {
        //如果为空,那么就会依次遍历,将数组中的空值移除
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        //如果不为空,那么就会进行遍历
        for (int index = 0; index < size; index++)
            //找到与传过来的值相匹配的元素地址,然后进行移除操作
            if (o.equals(elementData[index])) {
                //移除
                fastRemove(index);
                return true;
            }
    }
    return false;
}
    
    //在这个移除操作中
private void fastRemove(int index) {
    	//首先会把实际修改次数加1
        modCount++;
    	//然后计算移除的位置
        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
    }    

那么通过上面的案例,可能还是没有明白为什么会报错呢,程序都执行到了结尾了,数据也移除了,那么为什么还会报错呢??

请继续往下看,程序确实已经完成了移除操作,但是,程序还没有执行完,继续往下走,会再次进入迭代器的遍历当中,当遍历了一个元素之后,再次调用next方法,就可以看到next方法中首先出现的checkForComodification();,那么核心就在这个方法当中了,还记得之前在添加数据的时候,modCount会记录修改的次数,之前添加了三个元素,也就是modCount=3,然后将modCount的值赋给了expectedModCount,那么expectedModCount的值是不是也等于3,但是在刚刚移除元素的过程中,是不是有modCount++出现,那么这时候modCount是不是等于4,这样是不是一切都明了了,此时modCount != expectedModCount成立,就会报出刚刚的错误了。

if (modCount != expectedModCount)
    throw new ConcurrentModificationException();

第二个例子:和第一次是一样的代码,注意不同的是这次移除的是倒数第二个元素

ArrayList list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
Iterator it = list1.iterator();
while(it.hasNext()){
    String s = (String) it.next();
    if (s.equals("bbb")){
        list1.remove(s);
    }
}

前面的操作流程都是一样的,但是这次运行结果竟然不报错!!!!??

为什么不报错呢???

这属于是特殊情况,为什么这么说呢,根据刚刚的代码,再来分析一下思路,如果删除的是倒数第二个元素,那么在下一次迭代器遍历中,首先看到的判断条件是不是public boolean hasNext(){return cursor != size;},实际上就是这里出了问题,现在想一下,集合中一共有三个元素,在进行了移除操作,就剩下两个元素,而且在移除操作最后一步elementData[–size] = null,首先是将size进行–操作,这样就说明此时的size大小为2,而光标在上一次的遍历中是不是cursor = i + 1有这个操作,这时候cursor是不是也变成了2,那么hasNext这个判断条件就变成了false,既然变成了false,就会直接跳出遍历,程序并没有报错!!

第三个例子:和第一次一样的代码,不同的是这一次使用的是迭代器自带的移除方法

ArrayList list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
Iterator it = list1.iterator();
while(it.hasNext()){
    String s = (String) it.next();
    if (s.equals("ccc")){
        //注意,这里是迭代器自带的方法进行元素的删除
        it.remove();
    }
}

前面遍历操作的流程大致都是一样的,主要看一下这个迭代器自带的删除方法有什么异同

下面进行源码分析

public void remove() {
    //来看一下lastRet是什么?
    //之前在前面的代码中,是不是在遍历的next方法中出现,这个lastRet指向的就是,当前遍历的元素,也可以说是遍历到的最后一个元素(不是遍历完,而是遍历到当前的元素,当前元素的下表作为lastRet的值)。这个lastRet的值就是需要remove的那个元素的下标
    //首先判断这个下标是否小于0,如果是就会抛出IllegalStateException
    if (lastRet < 0)
        throw new IllegalStateException();
    //检查预期修改次数和实际修改次数是否相同
    checkForComodification();

    try {
        //调用集合本地的remove方法,将lastRet这个下标指向的值删除
        ArrayList.this.remove(lastRet);
        //将当前遍历的最后一个值得下标赋值给光标
        cursor = lastRet;
        //将lastRet置为-1
        lastRet = -1;
        //将实际修改的次数赋值给预期修改次数
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

public E remove(int index) {
    //校验下表是否越界
    rangeCheck(index);

    //实际修改次数加1
    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;
}

private void rangeCheck(int index) {
    if (index >= size)
        //如果索引下标大于集合大小,就会报错
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

总结:可以看到,通过迭代器的方法进行删除的时候,大致思路是一样的,而且底层的删除还是用的ArrayList自带的集合进行删除的,不过不同的是,迭代器的删除方法中**expectedModCount = modCount;**多出了这样的一个步骤,所以迭代器删除是不会报错的。

clear、contains、isEmpty源码

  1. clear方法:

    //简单明了
    public void clear() {
        //实际修改次数自增
        modCount++;
    
        // 通过遍历,将所有的值置为空,以便垃圾回收机制将它们进行回收
        for (int i = 0; i < size; i++)
            elementData[i] = null;
    
        //将数组的大小也置为0
        size = 0;
    }
    
  2. IsEmpty方法:

    //判断数组大小是否为0
    public boolean isEmpty() {
        return size == 0;
    }
    
  3. contains方法:

    ArrayList list1 = new ArrayList<>();
    list1.add("aaa");
    list1.add("bbb");
    list1.add("ccc");
    list1.contains("abc");
    
    //首先进入这个方法中,返回indexOf(o) >= 0比值是否大于0,如果大于就是包含相应的值,如果小于0就是不包含
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    
    //核心方法
    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;
            }
        //如果都没有找到,就返回-1
            return -1;
        }
    
    

最终可以通过判断**list1.contains(“abc”);**的返回值是ture还是false来判断是否包含所传入的值。

考虑以下几个问题

  1. ArrayList是如何扩容的?

  2. ArrayList频繁扩容导致添加性能几句下降,如何处理?
    只想到了使用初始容量的构造方法,不过浪费空间

  3. ArrayList插入或者删除元素一定比LinkedList慢吗?
    不一定,这个就要看是否针对某个位置进行相应的插入和删除操作,如果不针对位置进行插入和删除,那么肯定是LinkedList比较快的,如果针对位置,那么它们俩的性能是差不多的。

  4. ArrayList是线程安全的吗?

    不是线程安全的,那么又如果解决这个不安全,或者手写一个不安全的案例看看?

    如何解决线程不安全?

    使用Vector、使用synchronize、以及Collections.synchronizedList()、lock()

  5. 如何将某个ArrayList复制到另一个ArrayList中
    构造方法、clone、添加

  6. 多线程下保证正常读写?
    copyOnWriteArrayList

  7. ArrayList和LinkedList的区别?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值