`

源码分析(二)——ArrayList(基于JDK1.6)

 
阅读更多

一、ArrayList的定义:

public class ArrayList<E>

           extends AbstractList<E>  

           implements List<E>, RandomAccess, Cloneable, java.io.Serializable

从上述定义可以看出:

1. ArrayList继承自AbstractList,实现了List,所以它是一个队列,支持相关的添加、删除、修改、遍历等功能。

2. ArrayList实现了RandomAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。

3. ArrayList实现了Cloneable接口,即实现clone()函数。它能被克隆。

4. ArrayList实现了java.io.Serializable接口,支持序列化。

5. ArrayList是支持泛型的。

 

 

二、ArrayList的基本属性:

ArrayList只定义了两个基本属性:存放元素的数组和数组大小。

     /**
       * The array buffer into which the elements of the ArrayList are stored.
       * The capacity of the ArrayList is the length of this array buffer.
       */
      private transient Object[] elementData;
  
     /**
       * The size of the ArrayList (the number of elements it contains).
       *
       * @serial
       */
      private int size;

 

 

三、ArrayList的构造方法:

1. 第一个构造方法(不带参数):

 /**
  * Constructs an empty list with an initial capacity of ten.
  */
  public ArrayList() {
     this(10);
  }

 

2. 第二个构造方法(带一个参数:初始容量):

  /**
   * Constructs an empty list with the specified initial capacity.
   */
   public ArrayList(int initialCapacity) {
     super();
     if (initialCapacity < 0)
          throw new IllegalArgumentException("Illegal Capacity: "+
                                                initialCapacity);
     this.elementData = new Object[initialCapacity];
    }

 

3. 第三个构造方法(传入一个集合)

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     */
    public ArrayList(Collection<? extends E> c) {
     elementData = c.toArray();
     size = elementData.length;
     // c.toArray might (incorrectly) not return Object[] (see 6260652)
     if (elementData.getClass() != Object[].class)
         elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

 

 

四、ArrayList增加元素方法:

1.  add(E e):在数组末尾增加一个元素。 

public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

ensureCapacity():

public void ensureCapacity(int minCapacity) {

      modCount++;

      int oldCapacity = elementData.length;

      if (minCapacity > oldCapacity) {

         Object oldData[] = elementData;

         int newCapacity = (oldCapacity * 3)/2 + 1;

             if (newCapacity < minCapacity)

         newCapacity = minCapacity;

             // minCapacity is usually close to size, so this is a win:

             elementData = Arrays.copyOf(elementData, newCapacity);

      }

 }

增加modCount之后,判断minCapacity(即size+1)是否大于oldCapacity(即elementData.length),若大于,则调整容量为max((oldCapacity*3)/2+1,minCapacity),调整elementData容量为新的容量,即将返回一个内容为原数组元素,大小为新容量的数组赋给elementData;否则不做操作。

因此可以看出:

ArrayList容量的拓展将导致数组元素的复制,多次拓展容量将执行多次整个数组内容的复制。若提前能大致判断list的长度,调用ensureCapacity调整容量,将有效的提高运行速度。

另外,ArrayList每次容量扩展大概是原来的1.5倍,增长二分之一。

 

2. add(int index, E element):在指定索引处添加元素。 

 public void add(int index, E element) {
     if (index > size || index < 0)
         throw new IndexOutOfBoundsException(
         "Index: "+index+", Size: "+size);
 
     ensureCapacity(size+1);  // Increments modCount!!
     System.arraycopy(elementData, index, elementData, index + 1,
              size - index);
     elementData[index] = element;
     size++;
 }

首先判断指定位置index是否超出elementData的界限,之后调用ensureCapacity调整容量(若容量足够则不会拓展),调用System.arraycopy将elementData从index开始的size-index个元素复制到index+1至size+1的位置(即index开始的元素都向后移动一个位置),然后将index位置的值指向element。 

 

3. addAll(Collection<? extends E> c):将一个arrayList添加到另一个arrayList中。 

 public boolean addAll(Collection<? extends E> c) {
     Object[] a = c.toArray();
     int numNew = a.length;
     ensureCapacity(size + numNew);  // Increments modCount
     System.arraycopy(a, 0, elementData, size, numNew);
     size += numNew;
     return numNew != 0;
 }

先将集合c转换成数组,根据转换后数组的程度和ArrayList的size拓展容量,之后调用System.arraycopy方法复制元素到elementData的尾部,调整size。根据返回的内容分析,只要集合c的大小不为空,即转换后的数组长度不为0则返回true。

 

4. addAll(int index,Collection<? extends E> c):将一个arrayList添加到另一个arrayList的指定索引处。

 public boolean addAll(int index, Collection<? extends E> c) {
     if (index > size || index < 0)
         throw new IndexOutOfBoundsException(
         "Index: " + index + ", Size: " + size);
 
     Object[] a = c.toArray();
     int numNew = a.length;
     ensureCapacity(size + numNew);  // Increments modCount
 
      int numMoved = size - index;
      if (numMoved > 0)
         System.arraycopy(elementData, index, elementData, index + numNew,
                  numMoved);
 
      System.arraycopy(a, 0, elementData, index, numNew);
      size += numNew;
      return numNew != 0;
  }

先判断index是否越界。其他内容与addAll(Collection<? extends E> c)基本一致,只是复制的时候先将index开始的元素向后移动X(c转为数组后的长度)个位置(也是一个复制的过程),之后将数组内容复制到elementData的index位置至index+X。

 

 

 

五、ArrayList的删除元素方法:

1. remove(int index)方法:移除指定位置的元素。 

 public E remove(int index) {
     RangeCheck(index);
     modCount++;
     E oldValue = (E) elementData[index];
     int numMoved = size - index - 1;
     if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
     elementData[--size] = null; // Let gc do its work
      return oldValue;
 }

首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素。

 

2. remove(Object o)方法:移除指定元素的元素。

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

首先通过代码可以看到,当移除成功后返回true,否则返回false。remove(Object o)中通过遍历element寻找是否存在传入对象,一旦找到就调用fastRemove移除对象。为什么找到了元素就知道了index,不通过remove(index)来移除元素呢?因为fastRemove跳过了判断边界的处理,因为找到元素就相当于确定了index不会超过边界,而且fastRemove并不返回被移除的元素。下面是fastRemove的代码,基本和remove(index)一致。

fastRemove(): 

 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; // Let gc do its work
 }

 

3. removeRange(int fromIndex,int 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;
 }

执行过程是将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。

 

4. clear():清空ArrayList方法。

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

clear的时候并没有修改elementData的长度(好不容易申请、拓展来的,凭什么释放,留着搞不好还有用呢。这使得确定不再修改list内容之后最好调用trimToSize来释放掉一些空间),只是将所有元素置为null,size设置为0。即clear方法只是将数组里的元素置为null,ArrayList的大小置为0,并没有释放内存。

 

 

六、ArrayList的修改元素方法:

1.  set(int index,E element): 修改指定索引位置的元素的值。 

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

首先检查范围,用新元素替换旧元素并返回旧元素。

  

 

七、ArrayList的获取元素方法:

 1. get(int index):获取指定索引的元素。

public E get(int index) {
     RangeCheck(index); 
     return (E) elementData[index];
}

 先调用RangeCheck方法检测索引是否越界,然后直接根据数组下标获取数组元素,由此可以看出ArrayList是元素插入慢,但是查询快(数组的查询速度是很快的)。

 

 

 

 八、ArrayList的索引相关方法:

 1.  indexOf(Object):获取元素在ArrayList中索引。

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

 通过遍历elementData数组来判断对象是否在list中,若存在,返回元素的index([0,size-1]),若不存在则返回-1。

 

2. lastIndexOf():获取指定元素最后出现的索引值。

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

  采用了从后向前遍历element数组,若遇到Object则返回index值,若没有遇到,返回-1。

 

 

 

九、ArrayList的其他方法:

1.  isEmpty():判断ArrayList是否为空。

    public boolean isEmpty() {

         return size == 0;

    }

    直接返回size是否等于0。

 

2. size():获取ArrayList的大小。

     public int size() {

           return size;

      }

 

3. contains(Object):判断元素是否在ArrayList中。

 public boolean contains(Object o) {
     return indexOf(o) >= 0;
 }

 

 4. toArray():将ArrayList转换成数组。

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

调用Arrays.copyOf将返回一个数组,数组内容是size个elementData的元素,即拷贝elementData从0至size-1位置的元素到新数组并返回。

以下是泛型方式写法:

 public <T> T[] toArray(T[] a) {
     if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
     System.arraycopy(elementData, 0, a, 0, size);
     if (a.length > size)
         a[size] = null;
     return a;
  }

 如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同。所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。

 

 5. trimToSize():压缩存放元素数组的空间大小。  

 public void trimToSize() {
     modCount++;
     int oldCapacity = elementData.length;
     if (size < oldCapacity) {
             elementData = Arrays.copyOf(elementData, size);
     }
 }

由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大的情况,将出现空间的浪费。trimToSize将返回一个新的数组给elementData,元素内容保持不变,length很size相同,节省空间。

 

 

 

 十、ArrayList与LinkList的比较:

1. 实现方式:ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

2. 随机访问速度:因为ArrayList底层由数组实现,LinkedList是双向链表,所以ArrayList的随机访问速度要比LinkedList快。

3. 插入删除速度:因为ArrayList底层由数组实现,在0号位置插入时将移动list的所有元素,在末尾插入元素时不需要移动。LinkedList是双向链表,在任意位置插入元素所需时间均相同。所以LinkedList插入删除速度要比ArrayList快。

综上所述,在List中有较多插入和删除操作的情况下应使用LinkedList来提高效率,而有较多索引查询的时候使用ArrayList(使用增强型的for循环或Iterator遍历LinkedList效率将提高很多)

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    jdk1.6 源码jdk1.6 源码

    7. **集合框架**:JDK 1.6的`java.util`包包含了各种集合实现,如ArrayList、LinkedList、HashMap等。源码分析可以揭示它们的内部结构和操作算法,这对于理解性能和选择合适的集合类型很有帮助。 8. **网络编程**:...

    JDK1.6API中文版

    **JDK1.6 API中文版** Java Development Kit (JDK) 1.6是Java编程语言的一个重要版本,它包含了一系列的开发工具和Java运行环境。API(Application Programming Interface)是JDK的核心部分,提供了丰富的类库供...

    jdk1.6源码Ecplise添加

    Java开发工具JDK1.6源码的获取与分析对于深入理解Java语言的内部机制、优化编程技巧以及排查问题有着至关重要的作用。源码能够揭示出API背后的实现细节,帮助开发者提升技能,增强代码质量。以下将详细介绍如何在...

    JDK1.6源码

    **JDK 1.6 源码详解** JDK(Java Development Kit)1.6,也被称为Java SE 6,是Oracle公司发布的Java平台标准版的一个重要版本。这个版本的发布为Java开发者提供了大量的新特性和改进,使得Java编程更加高效、安全...

    jdk1.6源码包

    **Java Development Kit (JDK) 1.6 源码包详解** JDK 1.6源码包是Java编程语言的核心开发工具集,它包含了大量的类库和工具,为开发者提供了深入理解Java平台工作原理的机会。源码包允许程序员查看并研究Java API的...

    JDK1.6中文帮助文档四

    2. **集合框架**:JDK1.6进一步完善了集合框架,包括List、Set、Map等接口及其实现类,如ArrayList、LinkedList、HashSet、HashMap等。这些类提供了丰富的操作方法,使得数据组织和管理更为便捷。 3. **IO流**:JDK...

    JDK1.6——API文档完整版

    JDK1.6的集合框架是Java开发中的重要组成部分,包括List、Set、Map等接口以及ArrayList、LinkedList、HashSet、HashMap等实现类。这些集合类提供了丰富的数据存储和操作功能,例如添加、删除、查找元素,以及对集合...

    jdk1.6中文帮助文档

    2. **集合框架**:JDK1.6中的集合框架是Java编程中的重要组成部分,包括ArrayList、LinkedList、HashSet、HashMap等数据结构。这些集合类提供了丰富的操作方法,帮助开发者高效地存储和处理数据。 3. **网络编程**...

    jdk1.6中文api

    JDK 1.6中文API是Java开发的重要参考资料,它包含了Java 1.6版本的所有公共类、接口、枚举、异常以及它们的方法和属性,为开发者提供了详尽的解释和示例。本文将围绕JDK 1.6中文API,深入探讨其中的关键知识点,帮助...

    JDK1.6 源代码

    **JDK1.6源代码详解** JDK(Java Development Kit)是Oracle公司提供的Java编程环境,包含了编译器、运行时环境、调试工具和其他实用工具。JDK1.6,也被称为Java SE 6,是Java历史上的一个重要版本,发布于2006年,...

    JDK1.6中Arraylist,Vector,LinkedList源码

    在JDK 1.6中,ArrayList提供了线程不安全的高效操作,适合于非并发环境下的高性能读写操作。其优点在于随机访问快速,因为数组支持索引直接访问;缺点是插入和删除元素需要移动后续元素,效率较低。 Vector与...

    java jdk 1.6.zip

    - **泛型的类型推断(Type Inference for Generic Instance Creation)**:JDK 1.6引入了类型推断,使得使用匿名内部类创建泛型实例时更简洁,如`List&lt;String&gt; list = new ArrayList()`。 - **枚举类型实例的比较...

    JDK1.6 api中文文档HTML版

    《JDK1.6 API中文文档HTML版》是Java开发者的重要参考资料,它包含了JDK1.6版本的所有公共类和接口的详细说明。这个文档以HTML格式呈现,方便用户在线浏览或离线阅读,尤其对于中文环境下的开发者,中文版API文档能...

    jdk 1.6 带索引 api

    **JDK 1.6 带索引 API 知识点详解** JDK 1.6,也称为 Java Development Kit 1.6 或 Java SE 6,是Oracle公司发布的一个重要的Java平台版本,它在Java 1.5的基础上进行了一系列的改进和增强。这个版本的API带索引...

    jdk1.6.rar

    在本案例中,我们讨论的是JDK 1.6版本,这是一个较早但仍然在许多系统中使用的版本。 **Java环境配置**是使用Java技术的第一步。在Windows操作系统中,配置通常包括设置`JAVA_HOME`环境变量指向JDK的安装目录,然后...

    jdk1.6帮助文档中文版

    **JDK 1.6 帮助文档中文版** JDK(Java Development Kit)是Oracle公司提供的用于开发和运行Java应用程序的工具集合。版本1.6,也被称为Java SE 6,是Java平台标准版的一个重要里程碑。这个版本包含了Java编译器、...

    Java JDK1.6学习笔记

    Java JDK1.6学习笔记是一份详实的资料,涵盖了Java开发工具集(JDK)1.6版本的核心概念和技术。这份笔记对于Java初学者以及有一定经验的开发者来说都是一份宝贵的参考资料,它可能包含了从基础语法到高级特性的讲解...

    JDK1.6 API帮助文档

    12. **集合框架**:JDK1.6中的集合框架是`java.util`包的一大亮点,包括List、Set、Map接口,以及ArrayList、LinkedList、HashSet、HashMap等实现类。 13. **注解(Annotation)**:JDK1.6引入了注解,这是一种元...

    JDK1.6 API英文版all(CHM)

    **二、JDK 1.6新特性** 1. **增强的枚举(enum)**:在JDK 1.5引入枚举类型后,1.6版本进一步增强了枚举的使用,包括枚举常量之间的比较和遍历。 2. **泛型(Generics)**:泛型增加了类型安全,允许在编译时检查...

    JDK1.6 API帮助文档 中文版

    **JDK1.6 API帮助文档详解** JDK1.6 API(Application Programming Interface)是Java开发者的重要参考资料,它包含了Java开发工具包的所有类库、接口和方法的详细描述,为程序员提供了丰富的功能调用和编程指导。...

Global site tag (gtag.js) - Google Analytics