java集合详解之ArrayList

java集合详解之ArrayList,第1张

1 特点
  • 有序的,可以存储重复值和null值。
  • 底层是数组实现的,线程不安全。通过下标查询、速度快,非末尾增删的时候需要重新移动数据,所以增删性能低。
  • 调用构造函数new ArrayList()时,最开始是一个空数组,在第一次add的时候会创建一个初始容量为10的数组。也可以自定义初始化容量new ArrayList(int initialCapacity)。
  • 以自身的1.5倍容量扩容,不可以设置容量增量。
2 源码分析

2.1 接口的定义和属性
public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    /**
     * 默认的初始容量,在不指定minCapacity变量的时候,它将使用DEFAULT_CAPACITY 作为数组的大小。但是需要注意的是它是在第一次添加元素的时候在使用的,而不是像指定了minCapacity在构造方法中那样在创建ArrayList 的过程中就创建了。
     */
    private static final int DEFAULT_CAPACITY = 10;
    /**
     * 指定参数初始容量,但是初始容量是0的时候
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
     * 初始容量是0的数组。无参构造时候,elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA 其实主要是为了区分elementData=它是无参构造的赋值,当有参构造方法但是参数是0的时候的赋值
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
     * 实际存储元素的数组
     */
    transient Object[] elementData;
    /**
     * 实际存储的元素个数,而不是数组的大小
     */
    private int size;
}
2.2 构造方法
    //ArrayList构造方法
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    
    public ArrayList(Collection c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }
  • 当调用new ArrayList<>()时,将一个空数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给了elementData,这个时候集合的长度size为默认长度0,在第一次添加的时候就会把容量变为10;
  • 当调用new ArrayList<>(100)时,根据传入的长度,new一个Object[100]赋值给elementData,当然如果玩儿的话,传了一个0,那么将一个空数组 EMPTY_ELEMENTDATA 赋值给了elementData;
  • 当调用new ArrayList<>(new HashSet())时,可以传递任何实现了Collection接口的类,将传递的集合调用toArray()方法转为数组内赋值给elementData。
2.3 add(E element)
    //添加一个元素到列表的末尾
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);//在添加元素之前要保证数组大小可以容纳该元素 
        elementData[size++] = e;
        return true;
    }

    //如果是首次添加元素的话,szie=0 则 minCapacity=1
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    //计算容量大小
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果无参数构造的话,elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在第一次添加元素的时候将直接扩容到DEFAULT_CAPACITY,直接返回DEFAULT_CAPACITY默认容量10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //否则的话则是有参构造或者不是第一次添加元素,那么这里返回的就是size+1,也就是说扩容到所需的szie+1 即可
        return minCapacity;
    }
    //确保足够的容量
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 当实际需要的最小容量大于数组的容量,不满足要求的容量则进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    //扩容
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //右移动一位相当于除以2,新的容量大小就相当于原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果newCapacity 还是不足的话,直接使用minCapacity作为最小容量
        //当无参构造方法创建时第一次调用grow方法minCapacity=10,而newCapacity=0,所以无参构造方法默认的初始容量为10
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //创建一个容量为newCapacity的数组,并把数组elementData元素复制到新的数组里
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //容量最大值
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //当超出最大值进行处理
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
2.4 add(int index, E element)
    //插入指定元素到指定位置,如果当前待插入的位置有元素,则需要右移当前元素和其后的元素
    public void add(int index, E element) {
        // 检查位置的合法性        
        rangeCheckForAdd(index);
        //跟add里面的一样,保证数组容量足够
        ensureCapacityInternal(size + 1);
        // 移动当前位置和其后置的元素
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    // 检查位置的合法性
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
2.5 set(int index, E element)
    public E set(int index, E element) {
        //数组下标是否越界
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    //数组下标是否越界
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
2.6 get(int index)
    public E get(int index) {
        //数组下标是否越界
        rangeCheck(index);

        return elementData(index);
    }
2.7 remove(int index) 和 remove(Object o)
    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);
        //通过设置null值让GC起作用
        elementData[--size] = null;

        return oldValue;
    }


    //删除ArrayList中的值对象,其实和通过下标删除很相似
    //只是多了一个步骤,遍历底层数组elementData,通过equals()方法或 == (特殊情况下)来找到要删除的元素,获取其下标
    //最后调用remove(int index)一样的代码即可。
    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) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null;
    }

ArrayList 还提供了另外一个方法,就是removeIf(),它是一个函数式接口

List cityList = new ArrayList<>(2);
cityList.removeIf((String name )->name.equalsIgnoreCase("Bangalore")); 
2.8 iterator()
//移除失败的场景
@Test
public void itratorTest(){
    List cityList = new ArrayList<>(4);
    cityList.add("重庆");
    cityList.add("北京");
    cityList.add("上海");
    cityList.add("天津");
    Iterator itr = cityList.iterator();
    while(itr.hasNext()){
        String city = itr.next();
        if(city.equals("北京")){
            cityList.remove(city);
        }
        System.out.println(city);
    }
}

输出结果
重庆
北京
java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)


//移除成功的场景
@Test
public void itratorTest2(){
    List cityList = new ArrayList<>(4);
    cityList.add("重庆");
    cityList.add("北京");
    cityList.add("上海");
    cityList.add("天津");
    Iterator itr = cityList.iterator();
    while(itr.hasNext()){
        String city = itr.next();
        if(city.equals("北京")){
            itr.remove();
        }
        System.out.println(city);
    }
}
2.9 其他方法

int size() : 获取集合长度,通过定义在ArrayList中的私有变量size得到

boolean isEmpty():是否为空,通过定义在ArrayList中的私有变量size得到

boolean contains(Object o):是否包含某个元素,通过遍历底层数组elementData,通过equals或==进行判断

int lastIndexOf(Object o):返回最后一个元素为o的下标

Object[] toArray():转换成一个新得数组返回

void clear():集合清空,通过遍历底层数组elementData,设置为null

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/726762.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存