ArrayList的详解

来源:互联网 发布:英雄联盟视频软件 编辑:程序博客网 时间:2024/06/08 15:00

前言:ArrayList是java常见的集合,相信大家已经在很多场合使用过,他的优点是查询速度快,插入删除速度慢,这里我们一起来详细看一下ArrayList的设计。


1  arrayList类关系


         查看arrayList的类关系,就可以知道他继承了   AbstractList,  AbstractList是一个抽象类,他继承了  AbstractCollection,并且实现了List

2     那么我们从入口开始说

       

      2.1 这里我们new了一个ArrayList的对象,点进去查看他的构造方法可以发现

              
 List<String> list = new ArrayList<>();
    /**     * Constructs an empty list with an initial capacity of ten.     */    public ArrayList() {        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;    }

         他把一个空的对象数组赋给了当前数组,所以这个构造函数的作用就是产生了一个空的对象数组,这里就可以得知ArrayList是底层为数组的集合,他也具有数组的所有特性。

         2.2 我们先从第一个add方法开始看,add方法是List接口就定义的,这里ArrayList做了实现。

          
  /**     * Appends the specified element to the end of this list.     *     * @param e element to be appended to this list     * @return <tt>true</tt> (as specified by {@link Collection#add})     */    public boolean add(E e) {        ensureCapacityInternal(size + 1);  // Increments modCount!!        elementData[size++] = e;        return true;    }

    private void ensureCapacityInternal(int minCapacity) {        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);        }        ensureExplicitCapacity(minCapacity);    }    private void ensureExplicitCapacity(int minCapacity) {        modCount++;        // overflow-conscious code        if (minCapacity - elementData.length > 0)            grow(minCapacity);    }

        
         
         通过上图可以发现,SIZE是数组的元素的个数,通过构造方法我们可以发现该数组初始化长度是空的,放置不了对象,DEFAULT_CAPAITY是里面定义的一个初始化长度数组,为10,minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);通过这行代码可以发现,当前数组长度小于10的时候取10,大于10的时候取minCapacity,minCapacity也就是指当前数组长度+放入的元素个数,minCapacity - elementData.length > 0时,进行数组的扩容。
    
  /**     * Increases the capacity to ensure that it can hold at least the     * number of elements specified by the minimum capacity argument.     *     * @param minCapacity the desired minimum capacity     */    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);    }


上面的方法主要是取最新的数组长度,分为三步
    1 将当前的数组长度往右移1位,加上之前的长度得到最新数组长度
    2 如果得到的数组长度仍然放不上要添加的元素,则取最小应该的长度做为最新长度
    3 如果数组长度达到最长,则
    private static int hugeCapacity(int minCapacity) {        if (minCapacity < 0) // overflow            throw new OutOfMemoryError();        return (minCapacity > MAX_ARRAY_SIZE) ?            Integer.MAX_VALUE :            MAX_ARRAY_SIZE;    }

,然后调用Arrays.copyOf进行扩容,原理就是复制当前数组到新的数组中。
最后将元素放到数组中   
以上就是将元素放入arrayList所做的动作。
      

       2.3   我们来看一下indexOf的 api


   /**     * Returns the index of the first occurrence of the specified element     * in this list, or -1 if this list does not contain the element.     * More formally, returns the lowest index <tt>i</tt> such that     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,     * or -1 if there is no such index.     */    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;    }
上诉的方法就很简单了   如果参数是null则    循环==null来判断,如果不为null   则调用equals的方法的比较,具体为啥这么区分大家可以自行百度一下equals的方法,
       
     2.4 接下来我们看一下get()方法,大家都知道里面参数的    集合的下标
     
    public E get(int index) {        rangeCheck(index);        return elementData(index);    }

  /**     * Checks if the given index is in range.  If not, throws an appropriate     * runtime exception.  This method does *not* check if the index is     * negative: It is always used immediately prior to an array access,     * which throws an ArrayIndexOutOfBoundsException if index is negative.     */    private void rangeCheck(int index) {        if (index >= size)            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));    }
大家可以看到   在从数组中拿元素的时候进行了一次安全检查  调用了rangeCheck的方法,如果超过了数组元素的长度,就报索引越界的错误,这就是为啥我们程序中经常遇到的错误了.,检查完毕,再从数组中拿数据

2.5  我门接着看remove的方法
 /**     * Removes the element at the specified position in this list.     * Shifts any subsequent elements to the left (subtracts one from their     * indices).     *     * @param index the index of the element to be removed     * @return the element that was removed from the list     * @throws IndexOutOfBoundsException {@inheritDoc}     */    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; // clear to let GC do its work        return oldValue;    }
上诉方法 ,我们依然检查了索引越界的问题,接下来调用
E oldValue = elementData(index);
获取要删除的元素,
int numMoved = size - index - 1;
这里主要是计算一下要删除oldValue 这个元素的后面所有元素的个数是多少,比如 现在一个有0 1 2 3 4 一个5个元素,现在要删除2 元素,那么计算出来2后面有3,4一共俩个元素
,目地是为了移动3 4 的下标,这也就是说明了为啥ArrayList的插入删除效率慢,因为他要维护后面所有元素的索引。
将后面的元素向前移动一位,将要删除的元素放到数组最后一位,并且将数组长度减少一位,并且赋值为null,让GC收集器去删除
2.6 其他的方法大家可以去看下ArrayList了  比较简单了。

但是ArrayList的设计为线程不安全的,这里引用别的博客的表述

概要介绍 
首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。 如图,List接口下面有两个实现,一个是ArrayList,另外一个是vector。 从源码的角度来看,因为Vector的方法前加了,synchronized 关键字,也就是同步的意思,sun公司希望Vector是线程安全的,而希望arraylist是高效的,缺点就是另外的优点。 说下原理(百度的,很好理解): 一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成: 
1. 在 Items[Size] 的位置存放此元素; 
2. 增大 Size 的值。 
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1; 
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。 
示例程序:
细心的朋友会发现
transient Object[] elementData; 
数组是被transient修饰的,目地是为了当数组长度为10时  元素为5时   进行序列化的时候只会序列化数组为5的对象,所以这里反序列化的时候是不安全的,没有意义的,所以声明为不参与序列化













     





原创粉丝点击