zoukankan      html  css  js  c++  java
  • ArrayList用法详解与源码分析

    说明

    此文章分两部分,1.ArrayList用法。2.源码分析。先用法后分析是为了以后忘了查阅起来方便~~

    ArrayList 基本用法

    1.创建ArrayList对象

    //创建默认容量的数组列表(默认为10)
    ArrayList<E> a = new ArrayList<E>();
    
    //创建容量为initialCapacity的数组列表
    ArrayList<E> a = new ArrayList<E>(int initialCapacity);
    
    //用一个集合来初始化数值列表
    ArrayList<E> a = new ArrayList<E>(Collection<? extends E> c);  
    

    Mark:

    CapacitySize 时两个不同的概念:
    Size: ArrayList包含的元素个数。
    capacity : ArrayList的容量(默认为10)。它是一个大于或等于size的值。当它的值小于size是就会对ArrayList进行扩容实现可变长数组。简单来说,capacity就是为了实现可变长数组而设计的。 (具体分析请看下面的源码分析)

    2.ArrayList方法

    向ArrayList添加元素

    /***********a是上面创建的ArrayList对象***************/
    
    //添加一个元素
    a.add(E e);
    
    //在特定的位置添加元素,index后面的元素全部向后移一位
    a.add(int index, E e);
    
    //在ArrayList 后面 添加集合
    a.addAll(Collection<? extends E> c);
    
    //从特定的位置添加集合
    a.addAll(int index, Collection<? extends E> c);  
    

    获取ArrayList的元素

    //获取特定索引的元素
    a.get(int index);  
    

    Mark:

    遍历ArrayList: 一般使用for-each
    for(E e : a)
       dosomething;


    用for-each的主要优势:
    相对传统的for循环,for-each更简洁,不容易出错,而且没有性能的损失


    修改(更新)ArrayList的元素

    //用e替换换index位置的元素
    a.set(int index, E e);
    

    Mark:

    注意: 区分set与add的用法
    add 是添加一个新的元素,要开辟新的空间来保存元素
    set 是修改特定位置的元素,index位置必须要已经存在元素,否则会越界

    删除ArrayList的元素

    //删除索引为index的元素,后面的元素全都向前移一位
    a.remove(int index);
    
    /*删除在列表中和obj相等的第一个元素(首次出现),  
    * 就做动作,保持列表原来的状态
    * 比如:a [1, 3, 7, 5, 7],执行a.remove((Integer)7)后
    * 结果为:[1, 3, 5, 7]
    */
    a.remove(Object obj);
    
    //删除与集合c中元素相等的元素
    a.removeAll(Collection<?> c);
    
    //保留与集合c中元素相等的元素,然后把其他元素删除(与removeAll相反)
    a.retainAll(Collection<?> c);  
    
    //删除ArrayList的所有元素,没有removeAll();方法
    a.clear();
    

    ArrayList的其他一些比较常用的方法

    //获取ArrayList的元素个数
    a.size();
    
    //判断ArrayList是否包含某个元素
    a.contains(Object o)
    
    //判断ArrayList是否是空列表([]),是就返回true
    a.isEmpty();
    
    //获取ArrayList的子列表,(字串包含索引为fromIndex的元素,不包含toIndex元素)
    a.subList(int fromIndex, int toIndex);
    
    //返回ArrayList的基本数组形式
    a.toArray();
    
    /**ArrayList克隆,调用方法后,会返回a的一份复制。它与普通的复制(=)不同 
    *赋值后,两个引用指向同一个对象,会相互影响  
    *从ArrayList源码可知,是开辟新的空间来新建一个对象,再把数据复制到新建的对象。
    *所以,clone后会形成两个不同的对象,因此不会相互影响。
    */
    a.clone();
    

    Mark:

    a.remove(Object obj): 如果a是Integer的泛型列表在支持remove对象时要强制转换为Ingeger或者Object类型(参考上面例子),如果直接执行a.remove(7),就会调用 a.remove(int index)方法。


    ArrayList源码分析

    首先,看看ArrayList定义的属性

    private static final int DEFAULT_CAPACITY = 10;  
    private static final Object[] EMPTY_ELEMENTDATA = {};  
    transient Object[] elementData;   
    private int size;    
    

    DEFAULT_CAPACITY就是上面提到的默认容量大小,从属性定义可知,容量的默认大小为10。

    EMPTY_ELEMENTDATA一个空的数组对象,主要用来与elementData做比较,elementData是否为空

    elementData最核心的一个属性。它是ArrayList的庐山真面目,ArrayList之所以是可变长的,主要是ArrayList内部有elementData这个东东在搞鬼~~。 ArrayList可变长原理:当size大于capacity时,ArrayList就会调用扩容函数新建一个容量更大的elementData数组来代替原来的数组来实现扩容。
    总的来说,ArrayList的可变长功能主要时围绕着capacity与elementData来实现的

    实现可变长数组的主要方法:

    public void ensureCapacityInternal(int minCapacity)
    private void ensureExplicitCapacity(int minCapacity)
    private void grow(int minCapacity) //核心方法

    ensureCapacityInternal解析

    /**
     * 此函数大概就叫内部容量确定函数,主要是在每次添加元素这些增大size的
     * 动作里调用,用来判断需不需要增加capacity大小实现动态分配
     */  
    private void ensureCapacityInternal(int minCapacity) {
       /**
        *这个判断只要是针对使用new ArrayList<E>()构造对象设定,以免频繁的扩容。
        *如果if判断为真,那么就是使用new ArrayList<E>()构造对象的.  
        *此刻就要选一个最大的容量作为期待的最小容量,避免频繁扩容
        *详细解析看下面的例子!!
        */
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
    
        //调用最终确认方法,需不需要扩容由此方法决定
        ensureExplicitCapacity(minCapacity);
    }  
    

    if (elementData == EMPTY_ELEMENTDATA)作用解析

    首先,我们新建一个类(我的为MyList),把ArrayList的源码复制过来方便为所欲为~~,然后把一些报错除去。最后我们修改下代码:

    // 把ensureCapacityInternal的if语句注释掉,
    /*if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }*/  
    在**grow**方法里添加一个输出语句
         System.out.println("-----invoke grow()------------");
    

    写我们的测试类:

      public class Test {
    public static void main(String[] args) {
    
         MyList<Integer> i = new MyList<Integer>();
         MyList<Integer> i2 = new MyList<Integer>(10);
    
        System.out.println("------使用new MyList<Integer>()------ ");
        i.add(1);
        i.add(1);
        i.add(1);
        i.add(1);
    
        System.out.println("------使用new MyList<Integer>(10)------ ");
        i2.add(1);
        i2.add(1);
        i2.add(1);
        i2.add(1);
    }
    

    }
    输出:

    ———使用new MyList()———
    ——-invoke grow()——————
    ——-invoke grow()——————
    ——-invoke grow()——————
    ——-invoke grow()——————
    ———使用new MyList(10)———

    可以看出,如果不是使用if判断,使用new ArrayList()方法每次添加元素都会调用grow,而每次调用grow都会生成一个新的数组。所以为了效率内存考虑要添加if判断。

    ensureExplicitCapacity方法

    /**
     *此代码功能很简单,只是进行一个简单的判断,看是否需要扩容
     *如果需要的容量比实际的容量大就进行扩容
     */
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
    
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    

    grow(核心方法)

     /**
      *扩容的具体动作
      */
     private void grow(int minCapacity) {
    
        int oldCapacity = elementData.length;
    
        //定义扩容后的容量大小为原来容量的3/2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容后的容量还是比期待的容量小,那么使用minCapacity为扩容后的容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
    
        /*这里实现了动态数组的特性。
         *跟踪copyOf源码可以发现,实现方法:新建一个大小为newCapacity数组,
         *然后把elementData之前的元素复制到新的数组,
         *最后把新数组返回f赋值给elementData以实现动态数组
         */
        elementData = Arrays.copyOf(elementData, newCapacity);
    }  
    

    总结:

    根据源码分析,可以知道:

    1. ArrayList本质上是一个数组。只不过是数组上包装了一层华丽的外套而已。
    2. ArrayList动态数组功能主要思想是,每当数组的空间不够时,ArrayList会自动把缓冲区(elementData,我们叫他做缓冲区把~~)的数据复制到一个新的缓冲区里,(大小一般为原来的3/2),并令它覆盖原来的缓冲区成为自己的缓冲区。


    常用方法分析

    写到这发现文章太长了,所以决定常用方法就写几个算了,不全写了。常用方法的源码都很简单。大多是对elementData这个数组操作返回而已。

    add方法

    public boolean add(E e) {
       //此方法上面已介绍
        ensureCapacityInternal(size + 1);  
        //操作elementData,把e添加到数组里
        elementData[size++] = e;
        return true;
    }
    

    set方法

    public E set(int index, E element) {
        //检查是否越界,该函数代码很简单,就是简单判断index与size大小。
        rangeCheck(index);
    
        //以下代码跟add对比下就知道他们的区别
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }  
    

    总的来说,ArrayList的源码不算难,主要部分实现动态数组那部分,其他的方法主要还是在操作elementData数组。感觉ArrayList是一个很常用的数据结构,看懂源码对以后使用ArrayList肯定有不少帮助,而且解了ArrayList用起来也踏实:-D。

  • 相关阅读:
    Python笔记220151023
    B/S和C/S【转载Jane的博客 http://blog.sina.com.cn/liaojane】
    Java Queue
    Java 使用 .this与.new
    Java多态继承与清理
    Java 匿名类
    Java 内部类2
    java 中的多重继承
    Java 内部类
    Java 异常(自定义异常)
  • 原文地址:https://www.cnblogs.com/averey/p/4109165.html
Copyright © 2011-2022 走看看