zoukankan      html  css  js  c++  java
  • ArrayList源码分析

    简介

    List接口的可变大小数组实现(动态数组)。允许null值。此类与Vector大致等效,但它是不同步的。
    所有的操作大致是线性的,加入一个元素需要O(1)时间,加入n个元素需要O(n)时间。
    此实现未同步。如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改了该列表,则必须在外部进行同步。
    可以这样构造同步集合:Collections.synchronizedList(new ArrayList(…)) 或者使用Vector
    迭代器支持快速失败。

    类继承关系

    在这里插入图片描述
    Serializable (标志接口) 可以被序列化网络传输
    RandomAccess(标志接口)可以随机访问 并且for循环遍历优于迭代器
    Cloneable(标志接口)能被克隆
    继承AbstractList 抽象类 实现List接口

    属性

    //默认容量
    private static final int DEFAULT_CAPACITY = 10;
    
    //当size=0的时候设置元素
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    //用以实例化默认创建的实例
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    //存储元素的数组
    transient Object[] elementData;
    
    //集合中包含多少元素
    private int size;
    
    //最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    

    构造方法

    public ArrayList() {
    	//初始化元素为空数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
        	//如果传入容量大于0 创建一个对应长度的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        	//如果传入容量为0 使用默认数组初始化
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        }
    }
    
    public ArrayList(Collection<? extends E> c) {
       //将传入集合元素放入自己数据
       elementData = c.toArray();
       //设置size 并检查长度(这一行做了赋值比较 很妙)
       if ((size = elementData.length) != 0) {
           // 转换类型 c.toArray 返回的可能不是Object[]类型
           if (elementData.getClass() != Object[].class)
               elementData = Arrays.copyOf(elementData, size, Object[].class);
       } else {
           // 如果传入为空 将数据设置为空数组
           this.elementData = EMPTY_ELEMENTDATA;
       }
    }
    

    常用方法解析

    添加

    public boolean add(E e) {
    	//使容量够用
      	ensureCapacityInternal(size + 1);
        elementData[size++] = e;//将元素插入最后
        return true;
    }
    public void add(int index, E element) {
        ensureCapacityInternal(size + 1);//增加容量
        //将index之后数据往后移动一位
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;//设置index位置为传入数据
        size++;//增加数量
    }
    
    /**
     * 这下面都是对容量进行增加的判断
     */
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    //计算容量
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果元素为空数组 为默认容量和传入的容量中大的那个
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    //确认容量大小
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//增加操作次数
        // 如果传入的容量大于数组实际大小 就要增加数组大小了
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    //增加容量
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //默认扩充为原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩充1.5倍小于传入的容量 那么就使用传入的容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果容量大于最大允许容量 确定使用integet最大值还是使用最大数组容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 将数组数据复制到新的数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //巨大容量处理
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) throw new OutOfMemoryError();
        //如果容量大于MAX_ARRAY_SIZE 使用int最大值 否则使用max array
        return (minCapacity > MAX_ARRAY_SIZE)?Integer.MAX_VALUE:MAX_ARRAY_SIZE;
    }
    

    添加多个

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

    获取

    public E get(int index) {
        return elementData(index);
    }
    E elementData(int index) {
        return (E) elementData[index];//返回数组中指定位置的数据
    }
    

    是否包含

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    //从前往后找到第一个相同元素
    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;
    }
    

    移除

    public E remove(int index) {
        modCount++;
        E oldValue = elementData(index);
    	//将index之后的数据依次往前移动一位
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        elementData[--size] = null; // 将最后一位置空 这样垃圾回收器才能回收 并修正size 
        return oldValue;
    }
    //循环找出第一个相同数据并移除
    public boolean remove(Object o) {
       if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    

    交集

    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);//验证集合非空
        return batchRemove(c, true);
    }
    //批量删除数据 complemet为true 删除c中包含的数据 false 删除c中不包含的数据
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;//定义两个指针 读和写
        boolean modified = false;
        try {
        	//遍历本集合 元素包含在c中 就放入本集合 否则跳过
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // 正常来说r最后是等于size的,除非c.contains()抛出了异常
            if (r != size) {
            	 //把未读的元素都拷贝到写指针之后
                System.arraycopy(elementData, r, elementData, w, size - r);
                w += size - r;
            }
            if (w != size) {
                // 读指针后面的数据清空
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
    

    总结

    内部采用Object类型数组存储数据; 初始化时为空数组
    当长度不够的时候; 默认扩容原来一半的空间; 不会缩容
    随机访问元素快 直接通过下标就能获取到数据 时间复杂度O(1)
    插入和删除尾部数据比较快 平均复杂度O(1)
    插入和删除中间元素效率差 删除后要将后边数据放到依次前移 平均O(n)
    一般的可以认为: 遍历查询快 增加删除慢

  • 相关阅读:
    ubuntu下编译安装uWebSockets
    centos7安装python3
    linux设置库文件加载包含路径
    centos7安装mysql和mysql-connector-c++
    SVN查看提交日志的命令
    [转]select模型的一种技巧运用-libevent
    一道题回顾计算机数值存储方式-原码,反码,补码
    msyql判断记录是否存在的三种方法
    windows下vs2013使用C++访问redis
    linux下查看端口的连接数
  • 原文地址:https://www.cnblogs.com/paper-man/p/13284630.html
Copyright © 2011-2022 走看看