ArrayList源码阅读And手写ArrayList

ArrayList源码阅读和手写ArrayList

1.1 继承与实现

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • 继承了AbstractList抽象类,实现List接口,序列化和可克隆的接口。

1.2 数组类型

  • 底层有动态数组实现,所谓的动态即数组的容量可扩容或者缩容。初始容量为10;
/**
     * 初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;
    /**
    *存放元素的数组
    */
    transient Object[] elementData; 
    /**
     * The size of the ArrayList (the number of elements it contains).
     *数组大小
     * @serial
     */
    private int size;
  • transient :

    • 1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。

    • 2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。

    • 3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

  • 那么ArrayList是如何序列化的呢? 序列化是重写了writeObject()这个方法,保证只序列化elementData中有数据的那部分。反序列化是重写了readObject(). 那么elementDate到底有没有被序列化呢? 答案是 :序列化了,不过只序列化了 有数据的那一部分,从而节省了资源。
    来看看源代码吧


    /**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // 写出元素计数以及任何隐藏的内容
        //程序走到这里时 给expectedModCount 来达到记录的作用
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // 这里就可以看到 只写出了有数据的部分。即序列化这部分
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
         //当检测到不相等时 说明发生了并发错误,有其他程序对数据进行了修改或者插入就抛出异常
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
  • 这里顺便看一下ConcurrentModificationException()以及modCount这个东西。
  • 什么时候抛出ConcurrentModificationException?
    • 在迭代Array List或者HashMap时,尝试对集合做一些修改操作(例如删除元素),可能会抛出java.util.ConcurrentModificationException的异常。

    • 一个线程通常不允许修改集合,而另一个线程正在遍历它。 一般来说,在这种情况下,迭代的结果是未定义的。 某些迭代器实现(包括由JRE提供的所有通用集合实现的实现)可能会选择在检测到此行为时抛出此异常。 这样做的迭代器被称为故障快速迭代器,因为它们快速而干净地失败fast-fail)机制,而是在未来未确定的时间冒着任意的非确定性行为。

    • 此异常并不总是表示对象已被不同的线程同时修改。 如果单个线程发出违反对象合同的方法调用序列,则该对象可能会抛出此异常。 例如,如果线程在使用故障快速迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。

    • 故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速的操作尽可能地抛出ConcurrentModificationException 。

  • 看看代码迭代时修改是在干啥?
  List<String> list = new ArrayList<String>();
        list.add("A");
        list.add("B");
 
        for (String s : list) {
             //当我迭代到这里时 作出删除操作就会抛出:ConcurrentModificationException 
             //这个在java核心技术里面有讲到。
            if (s.equals("B")) {
                list.remove(s);
            }
        }
  • 产生异常的原因
    • ArrayList的父类AbstarctList中有一个域modCount,每次对集合进行修改(增添元素,删除元素……)时都会modCount++
      而foreach的背后实现原理其实就是Iterator(关于Iterator可以看Java Design Pattern: Iterator),等同于注释部分代码。在这里,迭代ArrayList的Iterator中有一个变量expectedModCount,该变量会初始化和modCount相等,但如果接下来如果集合进行修改modCount改变,就会造成expectedModCount!=modCount,此时就会抛出java.util.ConcurrentModificationException异常。
      • 来看看迭代器的源代码
/*
 *AbstarctList的内部类,用于迭代
 */
private class Itr implements Iterator<E> {
    int cursor = 0;   //将要访问的元素的索引
    int lastRet = -1;  //上一个访问元素的索引
    int expectedModCount = modCount;//expectedModCount为预期修改值,初始化等于modCount(AbstractList类中的一个成员变量)

    //判断是否还有下一个元素
    public boolean hasNext() {
            return cursor != size();
    }
    //取出下一个元素
    public E next() {
            checkForComodification();  //关键的一行代码,判断expectedModCount和modCount是否相等
        try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
        } catch (IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet == -1)
        throw new IllegalStateException();
            checkForComodification();

        try {
        AbstractList.this.remove(lastRet);
        if (lastRet < cursor)
            cursor--;
        lastRet = -1;
        expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
        throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }
    }

1.3构造函数

  • ArrayList有三个构造函数
    • 如果在构造函数传入了 参数,来指定容量,就分类讨论: 如果容量大于0:那么就直接new一个Obejct数组。如果等于0,那么数组是空数组。private static final Object[] EMPTY_ELEMENTDATA = {};,这与我们构造空参形成的数组是不同的。当我们不传入参数时,数组是默认的空数组private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    • 为什么这么设计呢? 因为在源码中,当我们添加一个元素,我们会调用calculateCapacity来计算数组的容量。计算的方式就是先判断数组是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果是,就取·Math.max(DEFAULT_CAPACITY, minCapacity);否则就直接返回, minCapacity
/**
*制定初始容量
*/
public ArrayList(int initialCapacity) {
       if (initialCapacity > 0) {
          //这个容量其实就是用来给elementData这个数组来申请空间时的大小
           this.elementData = new Object[initialCapacity];
       } else if (initialCapacity == 0) {
       //当这个容量为0时,那么这个数组就是个空数组咯
           this.elementData = EMPTY_ELEMENTDATA;
       } else {
       //如果小于0  则抛出异常咯
           throw new IllegalArgumentException("Illegal Capacity: "+
                                              initialCapacity);
       }
   }
   /**
   *不传入参数 则使用默认的空间大小
   */
    public ArrayList() {
     
       this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
   }
    /**
    *  构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
    */
    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.4 增删改查

  • 接下来就是增加数据,修改数据,删除数据,查询数据了,作为一个后端程序员,百分之九十的时间都是在操作数据,而对数据的操作离不开增删改查。
1.4.1 增加数据
  • ArrayList里面提供了5中增加数据的操作
    add(E e):在尾部添加元素,如果不扩容 时间复杂度是O(1)级别的,
 /**
    * 将元素e插入到列表的尾部
    *
    * @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;
   }

add(int index, E element) : 这个方法是非常耗费时间的。因为会进行元素的大量移动平均时间复杂度是O(n)级别的。如果你的数据需要经常的插入删除的话 最好选用LinkedList,他的插入和删除 操作当确定位置之后 时间复杂度是O(1)级别的,只需要改变相应节点的指针指向即可达到删除和插入操作,而无需移动大量的元素。

 /**
    *将元素插入到指定的位置
    * @param index index at which the specified element is to be inserted
    * @param element element to be inserted
    * @throws IndexOutOfBoundsException {@inheritDoc}
    */
   public void add(int index, E element) {
     //检测index是否合法
       rangeCheckForAdd(index);
       //扩容
       ensureCapacityInternal(size + 1);  // Increments modCount!!
       //将数组 index+1到size-index的元素后移
 
       System.arraycopy(elementData, index, elementData, index + 1,
                        size - index);
         //指定位置赋值
       elementData[index] = element;
       //数组大小加一
       size++;
   }

addAll(Collection<? extends E> c):按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。

/**
  * 将指定集合中的所有元素追加到
    * 此列表,按指定的集合的"数据器"。 此操作的行为是
    * 如果操作时修改了指定的集合,则未定义
    * 正在进行中。 (这意味着此调用的行为是
    * 未定义,如果指定的集合是此列表,这
    * 列表是非空的。
    *
    * @param c collection containing elements to be added to this list
    * @return <tt>true</tt> if this list changed as a result of the call
    * @throws NullPointerException if the specified collection is null
    */
   public boolean addAll(Collection<? extends E> c) {
     //先将集合c转换成数组。
       Object[] a = c.toArray();
       //数组长度
       int numNew = a.length;
       //对数组进行扩容 大小就是原来的大小加上新的数组大小
       ensureCapacityInternal(size + numNew);  
       //复制移动 最耗费时间的操作
       System.arraycopy(a, 0, elementData, size, numNew);
       //扩容后 数组大小肯定增加啦
       size += numNew;
       return numNew != 0;
   }

addAll(int index, Collection<?> extends E> c) :从index开始将集合c的元素插入进列表中。具体操作差不多了,无非就是多了一个index是否合理 ,然后将index后面的元素移动,移动多少呢?既然别人要插进来,那么就给他挪动个刚刚那么多的大小啦!直接看源码

 public boolean addAll(int index, Collection<? extends E> c) {
 //检查index是否合理
       rangeCheckForAdd(index);
//集合c转换为数组
       Object[] a = c.toArray();
       //要插入的数组长度
       int numNew = a.length;
       //扩容一下
       ensureCapacityInternal(size + numNew);  // Increments modCount
       //要移动的元素数量
       int numMoved = size - index;
       if (numMoved > 0)
       //继续消耗我们的大把青春去copy
           System.arraycopy(elementData, index, elementData, index + numNew,
                            numMoved);
//添加数组
       System.arraycopy(a, 0, elementData, index, numNew);
       //size增加一下
       size += numNew;
       return numNew != 0;
   }

set(int index, E element): 就是在指定的index处设置值,很简单操作

   public E set(int index, E e) {
   //检查越界
           rangeCheck(index);
           checkForComodification();
           //得到老值
           E oldValue = ArrayList.this.elementData(offset + index);
           //设置新值
           ArrayList.this.elementData[offset + index] = e;
         //返回原来值
           return oldValue;
       }
1.4.2 删除元素
  • ArrayList提供了
  • remove(int index):移除指定索引
 public E remove(int index) {
           rangeCheck(index);
           checkForComodification();
           E result = parent.remove(parentOffset + index);
           this.modCount = parent.modCount;
           this.size--;
           return result;
       }
  • remove(Object o):移除指定元素
public boolean remove(Object o) {
       //因为ArrayList中允许存在null,所以需要进行null判断
       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;
   }
  • removeRange(int fromIndex, int toIndex):移除从fromIndex到toindex这一段的元素
protected void removeRange(int fromIndex, int toIndex) {
       modCount++;
       int numMoved = size - toIndex;
       System
               .arraycopy(elementData, toIndex, elementData, fromIndex,
                       numMoved);

       // Let gc do its work
       int newSize = size - (toIndex - fromIndex);
       while (size != newSize)
           elementData[--size] = null;
   }
  • removeAll() :移除所有元素 四个方法进行元素的删除。
1.4.3查找操作

get(int index):得到对应索引的元素,非常的实现。

  public E get(int index) {
           rangeCheck(index);
           checkForComodification();
           return ArrayList.this.elementData(offset + index);
       }
1.4.4扩容操作
  • ensureCapacity(int minCapacity):我们在前面经常看到这样的一个方法ensureCapacity():这个就是扩容的方法,在每次增加操作时,我们不但要判断索引是否合理还要判断当前数组容量是否足够,当不足的时候,我们就会进行扩容操作。来直接看看源代码
 /**
*     增加此 ArrayList 实例的容量,如果
    * 必要,以确保它至少可以容纳元素的数量
    * 由最小容量参数指定。
    * 列表是非空的。
    * @param   minCapacity   the desired minimum capacity
    */
   public void ensureCapacity(int minCapacity) {
       int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
           // any size if not default element table
           ? 0
           // larger than default for default empty table. It's already
           // supposed to be at default size.
           : DEFAULT_CAPACITY;

       if (minCapacity > minExpand) {
           ensureExplicitCapacity(minCapacity);
       }
   }

  private void ensureExplicitCapacity(int minCapacity) {
       modCount++;

       // overflow-conscious code
       if (minCapacity - elementData.length > 0)
           grow(minCapacity);
   }
  • 不同的java版本源代码可能会有一些不同,同学们在学习的时候可以多查查资料,或者直接把源代码的注释翻译过来。看源代码是有非常多的好处,最最基本的就是代码规范,看看别人怎么命令的,还有就是,很多步骤都是被封装成对应的方法,不但实现了代码的复用,还是得代码更加的简洁。

自己手写简单版的Array List源代码

public class ArrayList<E> {
   private E[] data ;
   private int size;
    //支持泛型
   /**
    *构造函数,传入数组的容量 capacity 构造Array
    * @param capacity 容量
    */
   public ArrayList(int capacity){
       data=(E[]) new Object[capacity];
       size=0;
   }

   /**
    * capacity: 容量
    * 无参数的构造函数,默认数组的容量capacity=10;
    */
   public ArrayList(){
       this(10);
   }

   /**
    * 得到数组大小
    * @return size
    */
   public int getSize(){
       return  size;
   }

   /**
    * 得到数组容量
    * @return data.length
    */
   public  int getCapacity(){
       return data.length;
   }

   /**
    * 返回数组是否为空
    * @return boolean
    */
   public boolean isEmpty(){
       return  size==0;
   }
   public void addLast(E e){
//        if (size==data.length)
//            throw new IllegalArgumentException("AddLast failed.Array is full");
//        data[size]=e;
//        size++;
       add(size,e);
   }
  public void addFirst(E e){
       add(0,e);
  }
   /**
    * 在 index插入元素e
    * @param index 索引
    * @param e 元素
    */
   public void  add(int index,E e){
       if (size==data.length)
          resize(2*data.length);
       if (index<0|| index>size) 
           throw new IllegalArgumentException("AddLast failed.Require index >=0 and index<=size");

       //元素后移
       for (int i=size-1;i>=index;i--){
           data[i+1]=data[i];
       }
       data[index]=e;
       size++;
   }

   /**
    *
    */
  public E get(int index){
       if (index<0||index>=size)
           throw new IllegalArgumentException("Get failed.index is ");
       return data[index];
   }
  public void set(int index,E e){
       if (index<0||index>=size)
           throw new IllegalArgumentException("Get failed.index is ");
       data[index]=e;

   }

   /**
    * 数组是否包含了某个元素
    * @param e 元素
    * @return
    */
   public boolean contains(E e){
      for (int i=0;i<size;i++){
          if (data[i].equals(e))
              return true;
      }
      return false;
   }

   /**
    * 找到某个元素 返回元素的下标
    * @param e 元素
    * @return
    */
   public int find(E e){
      for (int i=0;i<size;i++){
          if (data[i].equals(e))
              return i;
      }
      return -1;
   }

   /**
    * 删除下标元素 并返回这个元素
    * @param index 数组下标
    * @return
    */
   public E remove(int index){
       if (index<0||index>=size)
           throw new IllegalArgumentException("Get failed.index is ");
      E ret=data[index];
       for (int i=index+1;i<size;i++)
           data[i - 1] = data[i];
       size--;
       data[size]=null;
       //缩容
       if(size==data.length/4&&data.length!=0)
           resize(data.length/2);
       return ret;
   }
   public E removeFirst(){
      return remove(0);
   }
   public E removeLast(){
       return  remove(size-1);
   }
   //删除指定元素
   public void removeElement(E e){
       int index=find(e);
       if (index!=-1)
         remove(index);
   }
   private void resize(int newCapacity){
       E[] newData=(E[]) new Object[newCapacity];
       for (int i=0;i<size;i++)
           newData[i]=data[i];
       data=newData;
   }
}

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值