Java源码之ArrayList

Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。

Set和List继承于它。

Set中不能包含重复的元素,也没有顺序来存放。

  注意:集合顺序分为:添加顺序和字典顺序

  所以Set集合并不是绝对无序的,部分集合如HashSet,TreeSet就会根据字典顺序排序,也就成有序的,而LinkedHashSet,CopyOnWriteArraySet就不会根据字典顺序排序,而是根据时间顺序排列。

List可以包含重复元素,是一个有序集合(这里说的有序是指添加顺序)。

Map是另一个接口,它和Collection接口没有关系。Map包含了Key-Value键值对,同一个Map里Key不能重复,而不同Key的Value是可以相同的。

                                                                                 


                                                          ArrayList的源码

一、成员变量

     ArrayList的成员变量:

  private static final int DEFAULT_CAPACITY = 10;                         //默认长度10
  private static final Object[] EMPTY_ELEMENTDATA = new Object[0];        //空元素数据数组
  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];  //默认容量空元素数据数组
  transient Object[] elementData;                //Object数组
  private int size;                              //elementData数组里面包含的数据长度。
  private static final int MAX_ARRAY_SIZE = 2147483639;   //最大数组大小

  数组用来存储我们的数据。也就是说,我们代码中的add的数据都会放在这个数组里面。
  那么由此我们可知,ArrayList内部是由数组实现。

二、构造函数

      ArrayList的构造方法:

public ArrayList(int var1) {
    if (var1 > 0) {
        this.elementData = new Object[var1];
    } else {
        if (var1 != 0) {
            throw new IllegalArgumentException("Illegal Capacity: " + var1);
        }

        this.elementData = EMPTY_ELEMENTDATA;
    }

}

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(Collection<? extends E> var1) {
    this.elementData = var1.toArray();
    if ((this.size = this.elementData.length) != 0) {
        if (this.elementData.getClass() != Object[].class) {
            this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);
        }
    } else {
        this.elementData = EMPTY_ELEMENTDATA;
    }

}

我们看到主要有三个构造方法:

(1)第一个构造方法需要传入一个int类型的变量。

        表示我们实例化一个ArrayList的时候想让ArrayList的初始化长度为多少。

        然后如果该变量大于0,那么new一个长度为传入值的对象数组。

        如果传入为0,那么等于EMPTY_ELEMENTDATA。这个变量我们上面讲过,就是实例化一个对象数组,内容为空。

        如果小于0,那么抛出异常。

(2)第二个构造方法是无参构造方法,直接等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

(3)第三个构造方法则我们另外一种常见的使用方法,

         比如处理化AList的时候想把BList的值传给AList。

         那么使用如下代码:

        

        首先调用了var1.toArray()方法将我们传入的集合元素变成一个数组来赋值给elementData数组。

        然后判断elementData数组里面是否有数据元素:

            1.如果有,那么再判断elementData数组类型是否为Object,不是的话,转为Object类型。

            2.如果没有元素,那么直接赋值为EMPTY_ELEMENTDATA。

至此三个构造方法就已经分析完了,基本上没有什么难度。

三,常见方法

      ArrayList的常见方法:

   (1)size()

public int size() {
    return this.size;
}

 就是将elementData数组中元素个数返回。

   (2)isEmpty()

public boolean isEmpty() {
    return this.size == 0;
}

 就是判断sizes是否等于0,即elementData数组中是否有元素。

   (3)add()

  第一个add方法:

public boolean add(E var1) {
    this.ensureCapacityInternal(this.size + 1);
    this.elementData[this.size++] = var1;
    return true;
}

  第一行是:判断数组大小,扩容

  第二行是:常见的数组赋值,将下标为size处的数组元素赋值为var1,然后size自加1。

//计算容量
private static int calculateCapacity(Object[] var0, int var1) {
    return var0 == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(10, var1) : var1;
}

//确保(elementData)内部容量
private void ensureCapacityInternal(int var1) {
    this.ensureExplicitCapacity(calculateCapacity(this.elementData, var1));
}

//确保扩展容量
private void ensureExplicitCapacity(int var1) {
    ++this.modCount;
    if (var1 - this.elementData.length > 0) {
        this.grow(var1);
    }

}
//扩容
private void grow(int var1) {
    int var2 = this.elementData.length;
    //扩大为原来的1.5倍,计算速度上讲,移位运算要比算术运算快
    int var3 = var2 + (var2 >> 1);
    if (var3 - var1 < 0) {
        var3 = var1;
    }

    if (var3 - 2147483639 > 0) {
        var3 = hugeCapacity(var1);
    }

    this.elementData = Arrays.copyOf(this.elementData, var3);
}

private static int hugeCapacity(int var0) {
    if (var0 < 0) {
        throw new OutOfMemoryError();
    } else {
        return var0 > 2147483639 ? 2147483647 : 2147483639;
    }
}

第二个add方法:

 将指定的下标处元素赋值为我们设定的值

public void add(int var1, E var2) {
    //检查指定下标范围
    this.rangeCheckForAdd(var1);
    //确认数组长度是否足够
    this.ensureCapacityInternal(this.size + 1);
    //数组拷贝,将我们要插入的位置开始的元素全部往后移了一个位置,然后把值插入到指定的位置。插入到指定位置,指定位置的旧值会往后移,并不会被覆盖。
    System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1);
    this.elementData[var1] = var2;
    ++this.size;
}

//检查指定的下标索引是否比elementData中拥有元素的数量大或者小于0,有问题则抛出异常。

private void rangeCheckForAdd(int var1) {
    if (var1 > this.size || var1 < 0) {
        throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));
    }
}
//src就是:源数组
//srcPos表明:从源数组的下标多少开始复制
//dest就是:目标数组
//destPos表明:复制源数组的数据到从目标数组的下标开始存放
//length就是:打算复制多少个源数组的值
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

   (4)clear()

public void clear() {
    ++this.modCount;

    for(int var1 = 0; var1 < this.size; ++var1) {
        this.elementData[var1] = null;
    }

    this.size = 0;
}

首先modCount自加,表示我们对list进行了操作次数。

然后for循环置空(null)即可。

最后设置size等于0。

   (5)remove()

  第一个remove方法:

  移除指定下标的元素

public E remove(int var1) {
    //检查下标是否有效
    this.rangeCheck(var1);
    //操作次数加1
    ++this.modCount;
    //将指定下标的元素取出
    Object var2 = this.elementData(var1);
    //计算出需要移动多少个元素,指的是从删除位置往后的元素,不包括删除位置的元素。
    int var3 = this.size - var1 - 1;
    if (var3 > 0) {
       //如果个数大于0,那么调用 System.arraycopy()方法将删除位置后的一个元素开始到最后的元素往前移动一个位置。
        System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var3);
    }
    //然后将size立马自减,然后将最后一个位置置为null(因为元素往前移动一位,那么最后一个元素往前移后,原来的最后一个位置值还存在没有被覆盖)。
    this.elementData[--this.size] = null;
    //最后返回旧的删除位置的元素值。
    return var2;
}

第二个remove()方法:

 直接移除掉某个元素

整个函数返回类型为boolean,true表示有这个对象删除成功。没有表示数组里没有这个对象,没有进行删除操作。

public boolean remove(Object var1) {
    int var2;
    //首先判断我们传入的object是否为空,如果为空,那么就for循环找到数组中值为null的元素,调用fastRemove()方法
    if (var1 == null) {
        for(var2 = 0; var2 < this.size; ++var2) {
            if (this.elementData[var2] == null) {
                this.fastRemove(var2);
                return true;
            }
        }
    } else {
      //remove()中如果传入的对象不为null,那么就是for循环找到这个值然后移除即可。
        for(var2 = 0; var2 < this.size; ++var2) {
            if (var1.equals(this.elementData[var2])) {
                this.fastRemove(var2);
                return true;
            }
        }
    }

    return false;
}

//这就是第一个remove()方法的简化版,取消了越界检查,并且设置返回类型为void,不再返回删除的旧值。

private void fastRemove(int var1) {
    ++this.modCount;
    int var2 = this.size - var1 - 1;
    if (var2 > 0) {
        System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var2);
    }

    this.elementData[--this.size] = null;
}

   (6)contains()

查询当前ArrayList是否包含某个对象

public boolean contains(Object var1) {
    return this.indexOf(var1) >= 0;
}
public int indexOf(Object var1) {
    int var2;
    //首先是对传入对象的判空。如果对象为空,还是一样的,for循环来查找elementData中第一个为null的元素,然后返回下标。
    if (var1 == null) {
        for(var2 = 0; var2 < this.size; ++var2) {
            if (this.elementData[var2] == null) {
                return var2;
            }
        }
    } else {
       //如果传入对象不为空,那么一样for循环查找第一个匹配元素,然后返回第一个匹配元素的下标。
        for(var2 = 0; var2 < this.size; ++var2) {
            if (var1.equals(this.elementData[var2])) {
                return var2;
            }
        }
    }
   //如果都找不到,那么就返回-1。
    return -1;
}

   (7)get()

    

进行一个下标的越界判断,然后返回elementData[index]元素。

   (8)set()

  

其实set(int index, E element)和add(int index, E element)方法很相似。

只是set是将指定位置的值直接覆盖掉。

add()则是将指定位置开始的元素往后全部后移一位,旧值不会被覆盖掉。

                                                                 总结 

1. ArrayLIst内部是由【数组】实现的。而且在存放数据的数组长度不够时,会进行扩容(即增加数组长度),在Java8中是默认扩展为原来的1.5倍

int var3 = var2 + (var2 >> 1);

2. 既然是数组:

       优点:查找某个元素很快,可以通过下标查找元素,查找效率高。

       缺点:每次删除,都会进行大量的数组元素移动,复制新的数组等。

                  增加元素如果长度不够,还要进行扩容。

                  因此添加删除效率低。

 如果我们在实际开发中能够清楚知道我们的数据量,建议创建ArrayList的时候指定长度,这样无需频繁增加数据时不断进行扩容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值