• ArrayList简介


    ArrayList简介

    ArrayList以数组为底层数据结构的集合,是一个动态的数组队列,就是说该类的容量可以增长,与一般的数组不同。

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

    可以看出Arraylist其继承AbstractList抽象类,而AbstractList也实现了 List接口。

    public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
    

    实现的接口:

    • List:表示该集合可以存储重复的元素,具有增删改查的功能。

    • RandomAccess:表明给类有随机访问元素的功能(数组下标实现)。

    • Cloneable:表明可以在堆中可以克隆出与对象一样的对象,并且两个对象地址都不一样,即对克隆出来的对象修改影响不了被克隆对象。

    API接口

    // Collection中定义的API
    boolean             add(E object)
    boolean             addAll(Collection<? extends E> collection)
    void                clear()
    boolean             contains(Object object)
    boolean             containsAll(Collection<?> collection)
    boolean             equals(Object object)
    int                 hashCode()
    boolean             isEmpty()
    Iterator<E>         iterator()
    boolean             remove(Object object)
    boolean             removeAll(Collection<?> collection)
    boolean             retainAll(Collection<?> collection)
    int                 size()
    <T> T[]             toArray(T[] array)
    Object[]            toArray()
    // AbstractCollection中定义的API
    void                add(int location, E object)
    boolean             addAll(int location, Collection<? extends E> collection)
    E                   get(int location)
    int                 indexOf(Object object)
    int                 lastIndexOf(Object object)
    ListIterator<E>     listIterator(int location)
    ListIterator<E>     listIterator()
    E                   remove(int location)
    E                   set(int location, E object)
    List<E>             subList(int start, int end)
    // ArrayList新增的API
    Object               clone()
    void                 ensureCapacity(int minimumCapacity)
    void                 trimToSize()
    void                 removeRange(int fromIndex, int toIndex)
    

    ArrayList的属性

    // 序列化id  
    private static final long serialVersionUID =8683452581122892189L;
    
    //默认容量
    private static final int DEFAULT_CAPACITY = 10;
    
    //当使用有参构造方法且参数为0时给elementData赋值空对象
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //当使用无参构造方法时给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;
    

    EMPTY_ELEMENTDATA与DEFAULTCAPACITY_EMPTY_ELEMENTDATA区别得结合构造方法及扩容函数才比较弄得清楚。

    MAX_ARRAY_SIZE为什么不是Integer.MAX_VALUE而是Integer.MAX_VALUE-8呢,原因是数组需要8个字节存储数组长度。

    ArrayList的构造方法

    ArrayList的构造方法有三种。

    public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
        
        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);
            }
        }
        
        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;
            }
        }
    

    无参构造方法:创建数组对象,默认为空的数组对象,长度为1,size=0,当一次添加元素时,回默认扩容到10。

    int的有参构造方法:根据用户自定义的容量初始化,当容量为0时,回给数组赋值EMPTY_ELEMENTDATA空数组。

    Collection的有参构造方法:先对Collection c进行toArray()转换为数组形式,若其数组长度不为0,还得对数组元素类型进行判断,不同就转换为Object。若数组长度为0就赋值EMPTY_ELEMENTDATA空数组。

    ArrayList的add方法

    add方法有4种(包括AbstractList里面的2种)

    在ArrayList定义的2种及使用的相关函数:

    //第一种(无索引的加入单个元素)
    public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
        
     //第二种(有索引加入单个元素)
        public void add(int index, E element) {
            //判断索引是否有效
            rangeCheckForAdd(index);
            
            ensureCapacityInternal(size + 1);  
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);//将索引后的index都往后移一位
            elementData[index] = element;//放入目标索引
            size++;
        }
        
     
        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //判断容器内elementData是否第一次初始化,且使用无参构造函数初始化,若是,则返回初始容量10.
            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;
            int newCapacity = oldCapacity + (oldCapacity >> 1);//右移一位,相当于十进制除以2,则扩容的大小为原来的1.5倍
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
        
        private void rangeCheckForAdd(int index) {
            if (index > size || index < 0)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    

    通过上面的构造函数,我们可以就之前那个问题DEFAULTCAPACITY_EMPTY_ELEMENTDATA、EMPTY_ELEMENTDATA的区别 了, 通过无参构造函数进行构建的容器,调用calculateCapacity方法,minCapacity会变成10,而有参构造函数,参数为0的情况,调用calculateCapacity方法,minCapacity则还是为1(0+1),所以在扩容的时候,前者扩容为10,而后者扩容为1。这就是两个属性的区别。

    还有两种add方法来自 AbstractList抽象类

    //第三种(有索引的加入多个元素)
    public boolean addAll(int index, Collection<? extends E> c) {
      		//检测下标是否正常
            rangeCheckForAdd(index);
            int cSize = c.size();
            if (cSize==0)
                return false;
    				//fast-fail机制检测
            checkForComodification();
            l.addAll(offset+index, c);
            this.modCount = l.modCount;
            size += cSize;
            return true;
        }
    //第四种(无索引的加入多个元素)
    public boolean addAll(Collection<? extends E> c) {
            return addAll(size, c);
    }
    
    private void rangeCheckForAdd(int index) {
      if (index < 0 || index > size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    private void checkForComodification() {
            if (this.modCount != l.modCount)
                throw new ConcurrentModificationException();
        }
    

    其实这两种的add方法跟前面两种没什么区别,这里就不做累述了,主要就是这里会抛出两个异常IndexOutOfBoundsException索引不正确与ConcurrentModificationException fail-fast失败这两个异常。

    想了解fail-fast机制的:java中的fail-fast(快速失败)机制

    ArrayList的remove方法

    • remove(int index)
    • remove(Object o)
    • removeRange(int fromIndex, int toIndex)
    • clear()
    • removeAll(Collection c)

    remove(int index)代码:

    public E remove(int index) {
      //检测索引是否正常
      rangeCheck(index);
      //fail-fast机制
      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 elementData(int index) {
            return (E) elementData[index];
    }
    

    remove(Object o)代码:

    //大概思路就是遍历整个数组,找到该元素就删除掉。值得注意就是要分null与非null
    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;
    }
    
    /*
         * Private remove method that skips bounds checking and does not
         * return the value removed.
         *这个注释的意思就是边界检查并不返回删除的值。
         *这就是与第一个remove方法不同的地方。
         */
    private void fastRemove(int index) {
      modCount++;
      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
    }
    

    removeRange(int fromIndex, int toIndex)代码:

    protected void removeRange(int fromIndex, int toIndex) {
      modCount++;
      int numMoved = size - toIndex;
      System.arraycopy(elementData, toIndex, elementData, fromIndex,
                       numMoved);
    
      // clear to let GC do its work
      int newSize = size - (toIndex-fromIndex);
      for (int i = newSize; i < size; i++) {
        elementData[i] = null;
      }
      size = newSize;
    }
    

    删除部分元素,使用System.arraycopy覆盖就完成此功能。

    clear()代码:

    public void clear() {
      modCount++;
    
      // clear to let GC do its work
      for (int i = 0; i < size; i++)
        elementData[i] = null;
    
      size = 0;
    }
    

    removeAll(Collection c)代码:

    //从列表中移除指定 collection 中包含的其所有元素(可选操作)。
    public boolean removeAll(Collection<?> c) {
      Objects.requireNonNull(c);
      return batchRemove(c, false);
    }
    
    private boolean batchRemove(Collection<?> c, boolean complement) {
      final Object[] elementData = this.elementData;
      int r = 0, w = 0;
      boolean modified = false;
      try {
        for (; r < size; r++)
          if (c.contains(elementData[r]) == complement)
            //collection有的直接抛弃
            elementData[w++] = elementData[r];
      } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) {
          System.arraycopy(elementData, r,
                           elementData, w,
                           size - r);
          w += size - r;
        }
        //整理数组。
        if (w != size) {
          // clear to let GC do its work
          for (int i = w; i < size; i++)
            elementData[i] = null;
          modCount += size - w;
          size = w;
          modified = true;
        }
      }
      return modified;
    }
    

    ArrayList查找方法

    public E get(int index) {
      //检查索引是否正常
      rangeCheck(index);
    	
      return elementData(index);
    }
    
    private void rangeCheck(int index) {
      if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    //根据数组根据索引获取值
    E elementData(int index) {
      return (E) elementData[index];
    }
    

    ArrayList修改方法

    public E set(int index, E element) {
      rangeCheck(index);
    		
      E oldValue = elementData(index);
      elementData[index] = element;
      return oldValue;
    }
    

    基本跟上面没什么区别,就不太累述了。

    ArrayList的trimToSize方法

    public void trimToSize() {
      modCount++;
      if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
      }
    }
    

    该函数就是去掉数组没有使用的空间,就是(elementData.length在size索引 之后的空间。这里有一个就是特殊情况就是size。

    ArrayList的toArray方法

    ArrayList提供了一个将List转为数组的方法,该方法有两个重载,分别是

    • Object[] toArray()
    • toArray(T[] a)

    **toArray() **代码:

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
    

    toArray(T[] a)代码:

    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
    

    第二个方法相对第一个方法可以要求数组类型,所以第一个方法容易抛出java.lang.ClassCastException错误。

    例子:

    ArrayList<String> list= new ArrayList<String>();
    	for(inti = 0 ; i <  10 ; i++) {
      	list.add( "" +i);
    }
    
    String[] array= (String[]) list.toArray();
    
    Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
    	at demo01.main(demo01.java:10)
    

    ArrayList的indexOf与lastIndexOf方法

    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;
      }
      //没找到会返回-1
      return -1;
    }
    
    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;
      }
      //没找到会返回-1
      return -1;
    }
    

    最后:

    ArrayList底层是有一个Object[]数组实现,具有实例化、随机访问访问、克隆的功能,其内部大量使用System.arraycopyArrays.copyOf函数来实现它的功能。经过通过分析代码,ArrayList并不是并发安全,虽然其内部有fail-fast机制来保证,也就是有出现并发操作导致数据问题我就抛出问题出来,所以该类在并发下并不能使用。

    补充

    System.arraycopy与Arrays.copyOf区别

    System.arraycopy代码:

    public static native void arraycopy(Object src,int srcPos, Object dest, int destPos,int length);
    /**
    src:原数组
    srcPos:原数数组起始位置
    dest:目标数组
    destPos:目标数组起始位置
    length:要复制数组元素个数
    
    该方法使用native,表示该方法是使用其他底层函数写的,相对来说更加高效。
    */
    

    Arrays.copyOf代码:

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
      @SuppressWarnings("unchecked")
      T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
      System.arraycopy(original, 0, copy, 0,
                       Math.min(original.length, newLength));
      return copy;
    }
    
    /**
    original:要复制的数组
    newLength:新数组的长度
    newType:新数组的类型
    */
    

    两个方法区别:

    • System.arraycopy是对目标数组来进行复制数据操作。

    • Arrays.copyOf是内部创建一个数组再进行复制操作,其内部也是需要调用System.arraycopy来进行操作。

    深入学习System.arraycopy:《System.arraycopy为什么快》

  • 相关阅读:
    学习软件工程的个人总结
    结对编程——黄金点游戏
    读取程序字符,行数,单词的个人程序
    c#代码分析
    Visual Studio2013的安装过程及练习测试
    小学三年级出题程序
    超级课程表的个人评价
    对软件工程的个人困惑
    个人附加作业
    个人最终总结
  • 原文地址:https://www.cnblogs.com/zitai/p/12960429.html
Copyright © 2020-2023  润新知