• Java 集合--ArrayList


    ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现。除该类未实现同步外,其余跟Vector大致相同。

    每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容器内存储元素的个数不能多于当前容量。当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。size(), isEmpty(), get(), set()方法均能在常数时间内完成,add()方法的时间开销跟插入位置有关,addAll()方法的时间开销跟添加元素的个数成正比。其余方法大都是线性时间。

    初始容量为10,按照1.5倍扩容。

    ArrayList 有个 trimToSize() 方法用来缩小 elementData 数组的大小,这样可以节约内存。考虑这样一种情形,当某个应用需要,一个 ArrayList 扩容到比如 size=10000,之后经过一系列 remove 操作 size=15,在后面的很长一段时间内这个 ArrayList 的 size 一直保持在 < 100 以内,那么就造成了很大的空间浪费,这时候建议显式调用一下 trimToSize() 这个方法,以优化一下内存空间。或者在一个 ArrayList 中的容量已经固定,但是由于之前每次扩容都扩充 50%,所以有一定的空间浪费,可以调用 trimToSize() 消除这些空间上的浪费。

    ArrayList的继承关系

    java.lang.Object
       ↳     java.util.AbstractCollection<E>
             ↳     java.util.AbstractList<E>
                   ↳     java.util.ArrayList<E>
    
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}

    ArrayList构造函数

    // 默认构造函数
    ArrayList()
    
    // capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。
    ArrayList(int capacity)
    
    // 创建一个包含collection的ArrayList
    ArrayList(Collection<? extends E> collection)

    ArrayList的API

    // Collection中定义的API
    boolean             add(E object)
    boolean             addAll(Collection<? extends E> collection)
    void                clear()
    boolean             contains(Object object)
    boolean             containsAll(Collection<?> collection)
    boolean             equals(Object object)
    int                 hashCode()
    boolean             isEmpty()
    Iterator<E>         iterator()
    boolean             remove(Object object)
    boolean             removeAll(Collection<?> collection)
    boolean             retainAll(Collection<?> collection)
    int                 size()
    <T> T[]             toArray(T[] array)
    Object[]            toArray()
    // AbstractCollection中定义的API
    void                add(int location, E object)
    boolean             addAll(int location, Collection<? extends E> collection)
    E                   get(int location)
    int                 indexOf(Object object)
    int                 lastIndexOf(Object object)
    ListIterator<E>     listIterator(int location)
    ListIterator<E>     listIterator()
    E                   remove(int location)
    E                   set(int location, E object)
    List<E>             subList(int start, int end)
    // ArrayList新增的API
    Object               clone()
    void                 ensureCapacity(int minimumCapacity)
    void                 trimToSize()
    void                 removeRange(int fromIndex, int toIndex)

    ArrayList源码分析

    基于jdk1.7

    ArrayList类的属性:

    private static final int DEFAULT_CAPACITY = 10; // 集合的默认容量
    private static final Object[] EMPTY_ELEMENTDATA = {}; // 一个空集合数组,容量为0
    private transient Object[] elementData; // 存储集合数据的数组,默认值为null
    private int size; // ArrayList集合中数组的当前有效长度,比如数组的容量是5,size是1 表示容量为5的数组目前已经有1条记录了,其余4条记录还是为空

    三个构造函数:

    public ArrayList(int initialCapacity) { // 带有集合容量参数的构造函数
        // 调用父类AbstractList的方法构造函数
        super();
        if (initialCapacity < 0) // 如果集合的容量小于0,这明显是个错误数值,直接抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity]; // 初始化elementData属性,确定容量
    }
    
    public ArrayList() { // 没有参数的构造函数
        super(); // 调用父类AbstractList的方法构造函数
        this.elementData = EMPTY_ELEMENTDATA; // 让elementData和ArrayList的EMPTY_ELEMENTDATA这个空数组使用同一个引用
    }
    
    public ArrayList(Collection<? extends E> c) { // 参数是一个集合的构造函数
        elementData = c.toArray(); // elementData直接使用参数集合内部的数组
        size = elementData.length; // 初始化数组当前有效长度
        // c.toArray方法可能不会返回一个Object[]结果,需要做一层判断。这个一个Java的bug,可以在http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652查看
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

    add(E e) 方法:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 调用ensureCapacityInternal,参数是集合当前的长度。确保集合容量够大,不够的话需要扩容
        elementData[size++] = e; // 数组容量够的话,直接添加元素到数组最后一个位置即可,同时修改集合当前有效长度
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) { // 如果数组是个空数组,说明调用的是无参的构造函数
            // 如果调用的是无参构造函数,说明数组容量为0,那就需要使用默认容量
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }    
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 如果集合需要的最小长度比数组容量要大,那么就需要扩容,已经放不下了
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) { // 扩容的实现
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 长度扩大1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 将数组拷贝到新长度的数组中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    add(int index, E element) 方法:

    这个方法的作用是 在指定位置插入数据,该方法的缺点就是如果集合数据量很大,移动元素位置将会花费不少时间。

    public void add(int index, E element) {
        rangeCheckForAdd(index); // 检查索引位置的正确的,不能小于0也不能大于数组有效长度
        ensureCapacityInternal(size + 1);  // 扩容检测
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index); // 移动数组位置,数据量很大的话,性能变差
        elementData[index] = element; // 指定的位置插入数据
        size++; // 数组有效长度+1
    }

    上图就表示要在容量为5的数组中的第4个位置插入6这个元素,会进行3个步骤:

    1. 容量为5,再次加入元素,需要扩容,扩容出2个白色的空间
    2. 扩容之后,5和4这2个元素都移到后面那个位置上
    3. 移动完毕之后空出了第4个位置,插入元素6

    remove(int index):

    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; // 清楚对应位置上的对象,让gc回收
      return oldValue;
    }

    比如要移除5个元素中的第3个元素,首先要把4和5这2个位置的元素分别set到3和4这2个位置上,set完之后最后一个位置也就是第5个位置set为null。

    remove(Object o)

    // 跟remove索引元素一样,这个方法是根据equals比较
    public boolean remove(Object o) {
        if (o == null) {
            // ArrayList允许元素为null,所以对null值的删除在这个分支里进行
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            // 效率比较低,需要从第1个元素开始遍历直到找到equals相等的元素后才进行删除,删除同样需要移动元素
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    clear

    清除list中的所有数据

    public void clear() {
      modCount++;
    
      // 遍历集合数据,全部set为null
      for (int i = 0; i < size; i++)
          elementData[i] = null;
    
      size = 0; // 数组有效长度变成0
    }

    set(int index, E element)

    用element值替换下标值为index的值

    public E set(int index, E element) {
      rangeCheck(index); // 检查索引值是否合法
    
      E oldValue = elementData(index); 
      elementData[index] = element; // 直接替换
      return oldValue;
    }

    get(int index)

    得到下标值为index的元素

    public E get(int index) {
        rangeCheck(index); // 检查索引值是否合法
    
        return elementData(index); // 直接返回下标值
    }

    addAll

    在列表的结尾添加一个Collection集合

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // 扩容检测
        System.arraycopy(a, 0, elementData, size, numNew); // 直接在数组后面添加新的数组中的所有元素
        size += numNew; // 更新有效长度
        return numNew != 0;
    }

    toArray

    根据elementData数组拷贝一份新的数组

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

    ArrayList的注意点

    1. 当数据量很大的时候,ArrayList内部操作元素的时候会移动位置,很耗性能
    2. ArrayList虽然可以自动扩展长度,但是数据量一大,扩展的也多,会造成很多空间的浪费
    3. ArrayList有一个内部私有类,SubList。ArrayList提供一个subList方法用于构造这个SubList。这里需要注意的是SubList和ArrayList使用的数据引用是同一个对象,在SubList中操作数据和在ArrayList中操作数据都会影响双方。
    4. ArrayList允许加入null元素
  • 相关阅读:
    IDEA激活
    Docker安装
    IDEA使用汇总
    tomcat服务器安装
    Java=》volatile的理解
    2020年2月24日09:06:11,Hash散列
    ES的使用
    Python安装技巧
    数据结构与算法Scala
    2019年12月13日_Flume采集数据到kafka配置使用过程
  • 原文地址:https://www.cnblogs.com/hesier/p/5571340.html
Copyright © 2020-2023  润新知