zoukankan      html  css  js  c++  java
  • Java底层类和源码分析系列-ArrayList底层架构和源码分析

    几个要点

    • ArrayList非线程安全;
    • ArrayList基于动态数组,是一种线性表。随机访问友好,其可以保证在 O(1) 复杂度下完成随机查找操作,插入和删除效率低;
    • ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;
    • 容量动态调节,有一套扩容和优化空间的机制,ArrayList 允许空值和重复元素,当往 ArrayList 中添加的元素数量大于其底层数组容量时,其会通过扩容机制重新生成一个更大的数组;
    • ArrayList继承了AbstractList,实现了List、RandomAccess、Cloneable、Serializable接口;
    • ArrayList扩容的长度是原长度的1.5倍;
    • 删除和插入需要复制数组,性能差(可以使用LinkindList替代),ArrayList适合读多写少的场景;


    定义

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

    成员属性

    //默认初始化容量
    private static final int DEFAULT_CAPACITY = 10;
    
    //默认的空的数组,这个主要是在构造方法初始化一个空数组的时候使用
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    //使用默认size大小的空数组实例,和EMPTY_ELEMENTDATA区分开来,
    //这样可以知道当第一个元素添加的时候进行扩容至多少
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    //ArrayList底层存储数据就是通过数组的形式,ArrayList长度就是数组的长度。
    //一个空的实例elementData为上面的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,当添加第一个元素的时候
    //会进行扩容,扩容大小就是上面的默认容量DEFAULT_CAPACITY
    transient Object[] elementData; // non-private to simplify nested class access
    
    //arrayList的大小
    private int size;
    
    // 数组最大长度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    构造方法

    无参构造函数
    如果不传入参数,则使用默认无参构建方法创建ArrayList对象,如下:

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

    注意:此时我们创建的ArrayList对象中的elementData中的长度是1,size是0,当进行第一次add的时候,elementData将会变成默认的长度:10.
    带int类型的构造函数
    如果传入参数,则代表指定ArrayList的初始数组长度,传入参数如果是大于等于0,则使用用户的参数初始化,如果用户传入的参数小于0,则抛出异常,构造方法如下:

        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);
            }

    包含集合,把collection对象的内容(可以理解为深拷贝)copy到elementData中

        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }

    扩容

    每次扩0.5倍进新容量newCapacity

       private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            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);
        }
    
        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

    /** 在元素序列尾部插入 */
    public boolean add(E e) {
        // 1. 检测是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 2. 将新元素插入序列尾部
        elementData[size++] = e;
        return true;
    }

    add主要的执行逻辑如下:
    1)确保数组已使用长度(size)加1之后足够存下 下一个数据
    2)修改次数modCount 标识自增1,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增长数组,grow方法会将当前数组的长度变为原来容量的1.5倍。
    3)确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。
    4)返回添加成功布尔值。

    /** 在元素序列 index 位置处插入 */
    public void add(int index, E element) {
        rangeCheckForAdd(index);
    
        // 1. 检测是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 2. 将 index 及其之后的所有元素都向后移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 3. 将新元素插入至 index 处
        elementData[index] = element;
        size++;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
     
            ensureExplicitCapacity(minCapacity);
        }

    remove

    public E remove(int index) {
        rangeCheck(index); //校验下标是否合法(如果index>size,旧抛出IndexOutOfBoundsException异常)
        modCount++;//修改list结构,就需要更新这个值
        E oldValue = elementData(index); //直接在数组中查找这个值
    
        int numMoved = size - index - 1;//这里计算所需要移动的数目
        //如果这个值大于0 说明后续有元素需要左移(size=index+1)
        //如果是0说明被移除的对象就是最后一位元素(不需要移动别的元素)
        if (numMoved > 0)
            //索引index只有的所有元素左移一位  覆盖掉index位置上的元素
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //移动之后,原数组中size位置null
        elementData[--size] = null; // clear to let GC do its work
        //返回旧值
        return oldValue;
    }
    //src:源数组   
    //srcPos:从源数组的srcPos位置处开始移动
    //dest:目标数组
    //desPos:源数组的srcPos位置处开始移动的元素,这些元素从目标数组的desPos处开始填充
    //length:移动源数组的长度
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
    
    //私有删除方法,不进行边界检查,不返回被删除元素
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //native方法,速度比for和clone都"fast"
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null;
    }
    
    //删除[fromIndex,toIndex)的元素
    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;//要移动的数量
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                numMoved);
    
        // 删除后,list 的长度
        int newSize = size - (toIndex-fromIndex);
        //将失效元素置空
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }
    
    //移除c集合中的元素
    public boolean removeAll(Collection<?> c) {
        //判断集合是否为空,否则抛出NPE
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
    
    //保留c集合中的元素
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }

    get

    public E get(int index) {
                rangeCheck(index);
                checkForComodification();
                return ArrayList.this.elementData(offset + index);
            }
    //顺序找,返回首先出现的位置,找不到返-1。O(n)
    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;
    }
    
    //逆序找,返回最后出现的位置,找不到返-1。O(n)
    
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
    
    //顺序找实现,根据返回值判断
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }


    遍历的4种方法

            ArrayList<String> list = new ArrayList<String>();
            list.add("a");
            list.add("b");
            list.add("c");
    方式1
    Iterator<String> it = strList.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
    方式2
    for (int i = 0; i < list.size(); i++) {
        list.get(i);
    }
    
    方式3
            for(String tmp:list){
                System.out.println(tmp);
            }
    方式4
            Consumer<String> consumer = (a) -> System.out.println(a);
            list.forEach(consumer);
  • 相关阅读:
    区分jquery中的offset和position
    如何让搜索框的键盘显示搜索按键?
    如何设置table的border-radius?
    如何让输入框自动使用英文输入法?
    如何隐藏数字输入框的上下箭头?
    autocapticalize和autocorrect
    防止注入
    sychronized和lock和区别
    各大公司Java面试题超详细总结
    n的阶乘
  • 原文地址:https://www.cnblogs.com/starcrm/p/12696113.html
Copyright © 2011-2022 走看看