ArrayList实现类源码解析

我们为什么需要ArrayList实现类?

  ArrayList是List接口的实现类,其底层的关键点用的其实是《数组》

  ArrayList主要特点为:查询效率高,增删效率低,线程不安全。因为日常使用,都不涉及多线程,并且以查询为主,所以ArrayList在日常开发中使用最多。

  下面通过源码,分析一下ArrayList类。

基本实现

  上面说到ArrayList其实是靠数组实现的,那具体是怎么样实现呢?请看源码:

    private static final int DEFAULT_CAPACITY = 10;  //默认的数组大小为10,及容器可以存10个元素

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    private int size; // size用来存储容器现在已有的元素个数,注意,并不代表数组大小    

	transient Object[] elementData;  //elementData,就是一个数组,后面的操作主要都是操作elementData
	//不带参数构造器
    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);
        }
    }

  通过构造器,可以看出底层就是操作一个Object类型的数组:elementData ,默认初始化大小为10。

增、刪、改、查

  容器嘛,用来存储数据的,那就离不开”增、刪、改、查“这里四个字。那就来逐一看ArrayList是如何实现实现的。

  有两种新增方法,第一种为不带索引的,只传入元素值,则会加在最后一个,源码如下:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!  保证没有超过数组界限,即时扩容
        elementData[size++] = e;  // 加上在数组最后一位,并且用于计数的变量size加1
        return true;
    }

  另一种为在指定索引新增,就是先将指定索引及以后的元素后移,再为指定索引的数组元素赋值:

    public void add(int index, E element) {
        rangeCheckForAdd(index); // 检查传入索引数值的有效性,避免索引为负数等异常情况

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index); //使用arraycopy,进行后移
        elementData[index] = element;  // 在指定索引位置赋值
        size++;  //用于计数的变量size加1
    }

  删也有两种方法,第一种为传入具体索引的:

    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);
        elementData[--size] = null; // clear to let GC do its work
        //用于计数的变量size减1,并且数组最后一个元素置为null

        return oldValue;
    }

  另一种为删除指定元素内容,其实就是先找到元素内容匹配的索引,然后根据索引来删除:

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) { //找到元素内容为null的索引
                    fastRemove(index);  //调用根据索引删除的方法
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) { //找到与元素内容匹配的索引
                    fastRemove(index); //调用根据索引删除的方法
                    return true;
                }
        }
        return false;
    }
    //fastRemove与remove一样,无返回值,private的,外界调用不到
    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; // clear to let GC do its work
    }

  修改ArrayList,其实就是修改数组里指定索引的元素:

    public E set(int index, E element) {
        rangeCheck(index); // 检查传入索引数值的有效性,避免索引为负数等异常情况

        E oldValue = elementData(index);
        elementData[index] = element; //修改指定位置的元素内容
        return oldValue;
    }

  查询ArrayList,其实就是返回数组里指定索引的元素:

    public E get(int index) {
        rangeCheck(index);// 检查传入索引数值的有效性,避免索引为负数等异常情况

        return elementData(index);
    }

扩容

  Java中的数组,一旦声明,长度是不可以改变的。为什么ArrayList可以无限增加呢?是怎样进行扩容的呢?请看源码:

	//当调用add,新增时,检查到需要扩容时调用
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length; //获取目前的长度
        int newCapacity = oldCapacity + (oldCapacity >> 1); //新长度为:目前 长度 + (目前长度/2),即扩容1.5倍
        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); //把旧数组里的值copy到新数组
    }

  扩容,其实就是新建了一个新数组,新数组声明的元素个数为旧数组的1.5被,然后将旧数组的内容copy到新数组里,再把新数组赋值给elementData。旧数组没用了,就等着被JVM回收咯。

toString()

  在父类:AbstractCollection中,重写了toString()方法:

    public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())  // 为空
            return "[]";

        StringBuilder sb = new StringBuilder();  
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }

  是通过使用迭代器遍历,迭代器的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];  //读取elementData数组
        }

  简单来说,就是通过StringBuilder,拼接数组遍历出来的元素实现的,如下:

	public String toString() {
		
		StringBuilder  sb = new StringBuilder();
		
		sb.append("[");
		for(int i=0;i<size;i++){
			sb.append(elementData[i]+",");
		}
		sb.setCharAt(sb.length()-1, ']'); 
		
		return  sb.toString();
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值