ArrayList剖析

1、底层元素

 /**
     * 默认的数组容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    //空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //容器的层是数组
    transient Object[] elementData; // non-private to simplify nested class access

    //记录实际的元素个数
    private int size;

2、 add方法

  //向容器中添加元素
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //首先;确保数组容量时够的
        elementData[size++] = e;
        return true;
    }

//看看 ensureCapacityInternal(size + 1);  中的方法
 //确保底层数组的容量够
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {//判断数组是不是空;如果时空则首次分配大小为DEFAULT_CAPACITY(10)
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);//进行数组容量的修改
    }

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++; //modCount表示内部的修改次数,modCount++当然是增加修改的次数

        if (minCapacity - elementData.length > 0)//需要的长度大于当前数组的长度,调用grow方法
            grow(minCapacity);
    }


 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//右移动一位相当于除以2,所以newCapacity相当于oldCapacity的1.5倍数

        if (newCapacity - minCapacity < 0)//如果扩展的1.5倍数还是小于minCapacity,就扩展为minCapacity
            newCapacity = minCapacity;

        if (newCapacity - MAX_ARRAY_SIZE > 0)//如果新的数组容量newCapacity大于数组能容纳的最大元素个数 MAX_ARRAY_SIZE 2^{31}-1-8
            newCapacity = hugeCapacity(minCapacity);

        // 底层进行数组的拷贝实现扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

add中关键就是进行底层数组的扩容,其扩容的核心思路:

  • 判断数组是不是空;如果时空则首次分配大小为DEFAULT_CAPACITY(10)
  • 需要的长度大于当前数组的长度,调用grow方法
    • 先尝试新的数组大小为原数组大小的1.5倍数;
    • 如果1.5倍满足不了,则扩展为数组需要的大小
    • 进行数组扩容
  • 添加元素

3、remove方法

public E remove(int index) {
        rangeCheck(index); //进行索引范围检查

        modCount++; //修改次数增加1
        E oldValue = elementData(index); //查找待删除的值

        int numMoved = size - index - 1;//计算要移动的元素的个数
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);
        elementData[--size] = null; // 将size减1,同时释放引用以便对原对象被垃圾回收

        return oldValue;
    }
  • 进行索引位置检查
  • 从index往后的元素都往前移动一位,实际调用System.arrayCopy方法移动元素
  • 将最后一个位置设为null,设置为null后不再引用原来的对象,如果原来对象也不再被其他对象引用,就可以被垃圾回收

4、迭代

4.1、迭代的陷阱

关于迭代器,有一种常见的误用,就是在迭代的过程中间调用容器的删除方法。比如,要删除一个整数``

 for(Integer i:list){
           if(i.equals(2)){
               list.remove(i);
           }
        } //会抛出  Exception in thread "main" java.util.ConcurrentModificationException

发生了并发修改异常,为什么呢?

因为迭代器内部会维护一些索引位置相关的数据,要求在迭代过程中,容器不能发生结构性变化,否则这些索引位置就失效了。

所谓结构性变化就是添加插入,删除元素,知识修改元素内容不算结构性变化

4.2、使用迭代器正确的删除

4.3、ArrayList中内部的Iterator

   public Iterator<E> iterator() {
        return new Itr();
    }
//返回迭代器
 private class Itr implements Iterator<E> {
        int cursor;       // 下一个要返回的元素位置
        int lastRet = -1; // 最后要返回元素的索引位置
        int expectedModCount = modCount;

expectedModCount表示期望的修改次数,初始化外部类当前的修改次数modCount;

每次发生结构性变化的时候modCount都会增加,而每次迭代器操作都会检查expectedCount是否与modCount相同,这样就能检测出结构性变化

查看hasNext

 public boolean hasNext() {
            return cursor != size;
        }

查看Next

 @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();//检查是否发生结构性变化
            
            int i = cursor;  //获取当前指针的位置
            
            if (i >= size)
                throw new NoSuchElementException();
            
            Object[] elementData = ArrayList.this.elementData;
            
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            
            cursor = i + 1;//当前指针后移动
            
            return (E) elementData[lastRet = i];
        }

remove方法

 public void remove() {//
     
            if (lastRet < 0)
                throw new IllegalStateException();
     
            checkForComodification();//检查是否结构性

            try {
                ArrayList.this.remove(lastRet);//调用ArrayList的remove方法
                cursor = lastRet; //更新cursor,lastRet
                lastRet = -1;//更新expectedModCount的值
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

它调用了ArrayList的remove方法,但同时更新了cursorlastRetexpectedModCount的值,所以他可以正确删除

5、ArrayList实现的接口

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

RandomAccess接口表示可以随机访问;可随机访问就是举杯类似数组那样的特性,数据在内存是连续存放的,根据索引值就可以直接定位到具体的元素,访问效率高

6、其他方法

6.1、构造器

ArrayList(int initCapacity):指定的大小initalCapacity初始化内部的数组大小

public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

c ArrayList()构建空的

public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }

ArrayList(Collection<? extends E> c):以一个已有的Collection构建,数据会新复制一份

6.2、返回数组

Object[] toArray()返回Object数组

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

public <T> T[] toArray(T[] a):返回对应类型的数组,如果参数数组长度足以容纳所有元素,就是用该数组,否则就创建一个新数组

 public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

7、特点

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值