zoukankan      html  css  js  c++  java
  • 《Java基础知识》Java ArrayList源码分析

    前言

    分析ArrayList 的源码为JDK8版本。

    源码分析

    我们先看看一个案例:

    public class test2 {
    
        public static void main(String[] args) {
            int index = 10000000;
            ArrayList arrayList = new ArrayList();
            LinkedList linkedList = new LinkedList();
            long time0 =  System.currentTimeMillis();
            for (int i = 0; i < index ; i++) {
                arrayList.add(i);
            }
            long time1 =  System.currentTimeMillis();
            System.out.println(time1 - time0);
            long time2 =  System.currentTimeMillis();
            for (int i = 0; i < index ; i++) {
                linkedList.add(i);
            }
            long time3 =  System.currentTimeMillis();
            System.out.println(time3 - time2);
        }
    }

    运行结果:(多次运行结果之后发现不一定谁插入快)

    第二种情况,给ArrayList 设置长度。

    public class test2 {
    
        public static void main(String[] args) {
            int index = 10000000;
            ArrayList arrayList = new ArrayList(index);
            LinkedList linkedList = new LinkedList();
            long time0 =  System.currentTimeMillis();
            for (int i = 0; i < index ; i++) {
                arrayList.add(i);
            }
            long time1 =  System.currentTimeMillis();
            System.out.println(time1 - time0);
            long time2 =  System.currentTimeMillis();
            for (int i = 0; i < index ; i++) {
                linkedList.add(i);
            }
            long time3 =  System.currentTimeMillis();
            System.out.println(time3 - time2);
        }
    }

    运行结果:

    无法得出结论谁插入快。

    我们来看看为什么ArrayList 插入也这么快呢? 先来看看ArrayList 的源码

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

    先分析ArrayList 的对象定义,发现继承AbstractList,实现 List<E>, RandomAccess, Cloneable, java.io.Serializable 四个接口。

    AbstractList : 抽象类,定义了list的公共抽象方法。

    List<E>: 接口, 定义了一些list的公共接口。

    RandomAccess:标记接口,表示实现该接口的子类支持角标访问,主要用于判断list 是否实现该接口来知道是否可以通过下标访问,做中间件非常常用。

    Cloneable:标记接口,表示实现该接口的子类可以进行克隆:https://www.cnblogs.com/jssj/p/13767756.html

    Serializable:标记接口,表示实现该接口的子类可以实现序列化和反序列化:https://www.cnblogs.com/jssj/p/11766027.html

    接下来我们看看ArrayList的add方法的源码

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 判断list的长度够不够,不够就扩容
        elementData[size++] = e;           //elementData 是list真正存储数据的数组
        return true;
    }

    ok. 我们看看ArrayLIst是如何扩容的。

        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {      // 这一步是判断ArrayList是在创建对象的时候是否传入指定长度。没有传入指定长度的.
                return Math.max(DEFAULT_CAPACITY, minCapacity);          // 则返回默认创建数组的长度,为了后面可以直接扩容。
            }
            return minCapacity;
        }
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            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);   //扩大1.5倍得到新长度。
            if (newCapacity - minCapacity < 0)                    //新获得的长度是否小于要出入的位置,如果小,则直接扩大到需要插入位置的大小。(不知道什么场景会进这里)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)                 // 数组的长度不能超过Integer 的长度-8. 8 指的是数组本身也需要暂用的空间。
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);  // 复制数组。真正的复制逻辑是native本地方法。
        }

    指定位置添加元素:

        public void add(int index, E element) {
            rangeCheckForAdd(index);         // 判断index是否有效
    
            ensureCapacityInternal(size + 1);  // 上面已经讲过原理。
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);    // 从该位置复制一份后面的值,全部往后移。
            elementData[index] = element;      // 最后在当前位置修改元素值。
            size++;
        }

    看看ArrayList 是如何删除的:

        public E remove(int index) {
            rangeCheck(index);    // 判断角标是否越界
            modCount++;           //操作计数器(用于迭代器迭代的时候如果这个list发送变化,能够及时感知到,提前报错,而不是获取错误数据)
            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  最后一位清空。 
    
            return oldValue;
        }

    关于操作计数器(modCount),再通过案例说明一下

    public class test2 {
    
        public static void main(String[] args) {
            int index = 100;
            ArrayList arrayList = new ArrayList(index);
            for (int i = 0; i < index ; i++) {
                arrayList.add(i);
            }
    
            Iterator<Integer> iterator = arrayList.iterator();
            while (iterator.hasNext()){
                int a =  iterator.next();
                if(a == 3){
                    arrayList.remove(a);      // list 删除了元素
                }
            }
        }
    }

    运行结果:

    从案例中我们可以看到,迭代的过程中是不允许删除或者添加元素的,修改没有问题,要保证长度不变。

    再来看一个案例

    public class test2 {
    
        public static void main(String[] args) {
            long[] long1 = new long[]{1,2,3,5};
            List arrayList1 = Arrays.asList(long1);
            System.out.println(arrayList1.size());
    
            Long[] long2 = new Long[]{1l,2l,3l,5l};
            List arrayList2 = Arrays.asList(long2);
            System.out.println(arrayList2.size());
        }
    }

    运行结果:

    我们要注意基本数据类型是不支持泛型化的。所以数组转list需要小心这种情况。

    扩展知识,不可变集合

    public class test2 {
    
        public static void main(String[] args) {
            //  不可变集合
            List list = Collections.unmodifiableList(Arrays.asList("2","5","7"));
            list.add("9");    // 该操作是不允许的
        }
    }

    运行结果:

    总结

    1. ArrayList 的底层数据结构使用的是数组。

    2. ArrayList是通过创建1.5倍长度的数组来进行扩容的。

    3. ArrayList被迭代的时候是不能改变原list的长度的。

    4. 使用Arrays.asList 方法的时候需要注意数组是否为基本类型。

  • 相关阅读:
    Spring 未初始化声明bean就ref,造成的异常
    数据库优化
    数据库删除操作尽量不要做物理删除,用状态字段来表示
    lua脚本语言
    上半年阅读书籍清单
    (转)从零实现3D图像引擎:(4)三角函数库
    (转)从零实现3D图像引擎:(1)环境配置与项目框架
    通信协议的正确处理方法
    (转)从零实现3D图像引擎:(6)向量函数库
    (转)从零实现3D图像引擎:(5)3D坐标系函数库
  • 原文地址:https://www.cnblogs.com/jssj/p/13767591.html
Copyright © 2011-2022 走看看