深入理解ArrayList

ArrayList简介

ArrayList是一个数组队列,相当于动态数组。与Java中的数组相比,他的容量能动态增长。它继承于AbstractList,实现了List,RandomAccess,Cloneable,java.io.Serializable这些接口。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
   //ArrayList继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修遍历等功能。

   //ArrayList实现了RandomAccess接口,即提供了随机访问机制。RandomAccess是java中用来被List实现,为List提供快速访问功能的。再ArrayList中,我们可以通过元素的序号快速获取元素对象,这就是快速访问机制。

   //ArrList实现了Cloneable接口,这意味着覆盖了函数clone(),能被克隆
   
   //ArrayList实现了java.io.Serializable接口,说明ArrayList支持序列化,能够通过序列化进行传输

   //ArrrayList不是线程安全的,建议再单线程中使用,多线程可以使用Vector或者CopyOnWritrArrayList

}

ArrayList属性

    //实现序列化接口时需要有序列化id
    private static final long serialVersionUID = 8683452581122892189L;
    //默认初始的容量
    private static final int DEFAULT_CAPACITY = 10;
    //一个空对象
    private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
    //一个空对象,如果使用默认构造函数创建,则默认对象内容是该值
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
    //当前数据对象存放的地方,当前对象不参与序列化
    transient Object[] elementData;
    //当前数组大小
    private int size;
    //数组最大长度
    private static final int MAX_ARRAY_SIZE = 2147483639;

ArrayList构造方法

    //有参构造   elementData是用于存放数组
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //初始值大于0,创建一个初始值大小的Object数组
            this.elementData = new Object[initialCapacity];
        } else {
            //数组小于0抛出异常
            if (initialCapacity != 0) {
                throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
            }
            //等于0,则为空对象
            this.elementData = EMPTY_ELEMENTDATA;
        }

    }
    //无参构造
    public ArrayList() {
        //如果不传入参数,创建一个空对象 = new Object[0]
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

        //注意:此时我们创建的ArrayList对象中的elementData中的长度是1,size是0,当进行第一次add 
          的时候,elementData会变成默认长度:10
    }
    //带Collection对象的构造函数,按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。
    public ArrayList(Collection<? extends E> c) {
        //将集合变量c运用.toArray()转换为数组,后将数组地址赋值给elementData
        this.elementData = c.toArray();
        //将elementData长度赋值给size,判断size的大小
        if ((this.size = this.elementData.length) != 0) {
            //this.elementData = arg0.toArray(),这里执行的简单赋值时浅拷贝,所以要执行深拷贝
            if (this.elementData.getClass() != Object[].class) {
                this.elementData = Arrays.copyOf(this.elementData, this.size, 
                Object[].class);
            }
        } else {
            //等于0直接将空对象的地址赋值给elementData
            this.elementData = EMPTY_ELEMENTDATA;
        }

    }

add方法

jdk1.8版本:add方法有两个,一个是带一个参数的,一个是带两个参数的

add(E e)方法

添加元素方法入口

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

确保添加的元素有地方存储,当第一次添加元素的时候this.size+1的值是1,所以第一次添加的时候会将当前elementData数组的长度变为10


    private void ensureCapacityInternal(int minCapacity) {
        //判断是否相同,因为第一次elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //第一次添加时DEFAULT_CAPACITY大于,其值为10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //不是第一次
        ensureExplicitCapacity(minCapacity);
    }

将修改次数(modCount)自增,判断是否需要扩充数组长度,判断条件就是当前所需的数组最小长度与数组的长度的对比,如果大于零,则增长数组长度。

private void ensureExplicitCapacity(int minCapacity) {
        //修改次数自增
        modCount++;

        // overflow-conscious code
        //判断当前所需数组最小长度是否大于数组长度,如果大于,则自增
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

如果当前数组已使用空间(size)加一之后大于数组长度,则增大数组容量,扩大到原来的1.5倍

private void grow(int minCapacity) {
        // overflow-conscious code
        //之前数组长度
        int oldCapacity = elementData.length;
        // >>右移位运算符,将一个数的各二进制全部右移若干位,正数左补零,负数左补1,右边丢弃,操 
           作数每右移一位,相当于该数除以2,所以此时newCapacity为原来oldCapacity的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果newCapacity还是小于minCapacity,赋值
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //新数组是否大于最大数组大小
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //调用hugeCapacity方法
            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) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

add(int index,E element)方法

执行逻辑:

#确保这个数插入的位置小于等于当前数组长度,并且不小于0,否则抛出异常

#确保数组已使用长度size+1之后足够存下下一个数据

#修改自增次数,如果当前数组已使用长度size+1后的大于当前的数组长度,则调用grow方法,增长数组

#grow方法会将当前数组的长度变为原来的1.5倍

#确保有足够的容量之后,使用System.arraycopy将需要插入的位置index后面的元素统统后移一位

#将新的数据存放到数组的指定位置index上

 public void add(int index, E element) {
        //确保index小于等于当前数组长度,并且不小于0,否则抛出异常
        rangeCheckForAdd(index);
        
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //确保有足够的容量之后,使用System.arraycopy将需要插入的位置index后面的元素统统后移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //将新的数据内容放到指定位置
        elementData[index] = element;
        //长度加一
        size++;
    }

注意:使用该方法的话导致指定位置后面的数组元素全部重新移动,即先后移动一位,这里用到了数据结构的内容。

get方法

返回指定位置上的元素

 public E get(int index) {
            //index超出范围报异常
            rangeCheck(index);
            //判断modCount是否相同
            checkForComodification();
            //返回结果
            return ArrayList.this.elementData(offset + index);
        }

set方法

确保set位置小于当前数组的长度size并且大于0,获取指定位置index元素,然后放到oldValue存放,将需要设置的元素放到指定的位置index上,然后将原来位置上的元素oldValue返回给用户。

 public E set(int index, E element) {
        //判断是否出界
        rangeCheck(index);
        //获取index本来的元素
        E oldValue = elementData(index);
        //新元素存放到elementData中
        elementData[index] = element;
        return oldValue;
    }

contains方法

调用indexOf方法,遍历数组中的每一个元素作对比,如果找到该元素,返回true否则false

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;
        }
        //错误返回
        return -1;
    }

remove方法

根据索引remove

代码逻辑:

#判断索引有没有越界

#自增修改次数

#将指定位置index上的元素保存到oldValue

#将指定位置index上的元素都往前移动一位

#将最后面的一个元素置空,让垃圾回收器回收

#将原来oldValue值返回

注意:置空并不会改变数组长度,只是将最后一个元素置空

public E remove(int index) {
        //判断是否越界
        rangeCheck(index);
        //自增修改次数
        modCount++;
        //将原来index位置上的元素赋值给oldValue
        E oldValue = elementData(index);
        //移动的数字
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //数组拷贝,index+1原数组位置,index目标数组位置,numMoved数组长度
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //最后一个元素置空
        elementData[--size] = null; // clear to let GC do its work
        //返回
        return oldValue;
    }

根据对象remove

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])) {
                    //还是根据index来删除的元素
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

clear方法

添加操作次数modCount,将数组内的元素都置空,等待垃圾回收器手机,不减小数组容量

public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

sublist方法

  • 返回指定的fromIndex (包含)和toIndex (独占)之间的此列表部分的视图。 (如果fromIndextoIndex相等,则返回的列表为空。)返回的列表由此列表支持,因此返回列表中的非结构更改将反映在此列表中,反之亦然。 返回的列表支持所有可选的列表操作。此方法消除了对显式范围操作的需要(对于数组通常存在的排序)。 任何需要列表的操作都可以通过传递subList视图而不是整个列表来用作范围操作。 例如,以下习语从列表中删除了一系列元素: list.subList(from, to).clear();
  •  public List<E> subList(int fromIndex, int toIndex) {
            //判断
            subListRangeCheck(fromIndex, toIndex, size);
            return new SubList(this, 0, fromIndex, toIndex);
        }
    
    
    SubList(AbstractList<E> parent,
                    int offset, int fromIndex, int toIndex) {
                this.parent = parent;
                this.parentOffset = fromIndex;
                this.offset = offset + fromIndex;
                this.size = toIndex - fromIndex;
                this.modCount = ArrayList.this.modCount;
            }

    iterator方法

  • iterator方法返回的的时一个内部类,由于内部类的创建默认含有外部的this,所以这个内部类可以调用到外部类的属性

  •     public Iterator<E> iterator() {
                return listIterator();
            }

    一般调用完iterator之后,我们会使用iterator做遍历,这里使用next做遍历的时候有个需要注意的地方,就是调用next的时候,可能会发生ConcurrentModificationException,当修改次数与期望的修改次数不一致的时候,会发生该异常。

  •  public E next() {
                        checkForComodification();
                        int i = cursor;
                        if (i >= SubList.this.size)
                            throw new NoSuchElementException();
                        Object[] elementData = ArrayList.this.elementData;
                        if (offset + i >= elementData.length)
                            throw new ConcurrentModificationException();
                        cursor = i + 1;
                        return (E) elementData[offset + (lastRet = i)];
                    }

    总结回顾:

  • 序列化,反序列化掌握不足

  • 泛型

  • 迭代器

  • 深拷贝,浅拷贝

  • Object类 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值