ArrayList源码详细解析分析

目录[-]

1.概述

  ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。

   每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小(如果不指定capacity默认是10)。

  

?
1
2
3
4
5
6
    /**
      * Constructs an empty list with an initial capacity of ten.
      */
     public  ArrayList() {
     this ( 10 );
     }

 

  随着向ArrayList中不断添加元素,其容量也自动增长自动增长会带来数据向新数组的重新拷贝(影响性能),因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。 

   

?
1
2
3
4
5
6
7
8
9
10
11
     /**
      * 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) {
     ensureCapacity(size +  1 );   // Increments modCount!!
     elementData[size++] = e;
     return  true ;
     }

 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    /**
      * Increases the capacity of this <tt>ArrayList</tt> instance, if
      * necessary, to ensure that it can hold at least the number of elements
      * specified by the minimum capacity argument.
      *
      * @param   minCapacity   the desired minimum capacity
      */
     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);
     }
     }

 

   注意: 在ensureCapacity中,

?
1
Object oldData[] = elementData;

 

    是要在复制新数组前,保存原来数组的引用,因为后面这个引用会指向新的数组。但是保存后其实没有用处。

  

?
1
    elementData = Arrays.copyOf(elementData, newCapacity);

 

   注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。

 

2 Arraylist的实现

 

2.1 底层利用object数组实现

?
1
   private  transient  Object[] elementData;

 

2.2 构造方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public  ArrayList() {
     this ( 10 );
}
 
public  ArrayList( int  initialCapacity) {
     super ();
     if  (initialCapacity <  0 )
         throw  new  IllegalArgumentException( "Illegal Capacity: " + initialCapacity);
     this .elementData =  new  Object[initialCapacity];
}
 
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 );
}

 

此处也是用Arrays.copyOf实现数组的复制,重点研究一下。与生成新数组的时候一样。

2.3  存储

第一判断ensureSize,如果够直接插入,否则按照policy扩展,复制,重建数组。

第二步插入元素。

ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法。下面我们一一讲解

1. set(int index, E element),取代,而非插入,返回被取代的元素

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
     /**
      * Replaces the element at the specified position in this list with
      * the specified element.
      *
      * @param index index of the element to replace
      * @param element element to be stored at the specified position
      * @return the element previously at the specified position
      * @throws IndexOutOfBoundsException {@inheritDoc}
      */
     public  E set( int  index, E element) {
     RangeCheck(index);
 
     E oldValue = (E) elementData[index];
     elementData[index] = element;
     return  oldValue;
     }

 

2. add(E e) 增加元素到末尾,如果size不溢出,自动增长

?
1
2
3
4
5
6
7
8
9
10
11
     /**
      * 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) {
     ensureCapacity(size +  1 );   // Increments modCount!!
     elementData[size++] = e;
     return  true ;
     }

 

3. add(int index, E element) 增加元素到某个位置,该索引之后的元素都后移一位

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
     /**
      * Inserts the specified element at the specified position in this
      * list. Shifts the element currently at that position (if any) and
      * any subsequent elements to the right (adds one to their indices).
      *
      * @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) {
     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++;
     }

 

3.后面两个方法都是把集合转换为数组利用c.toArray,然后利用Arrays.copyOF 方法,重点研究一下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    /**
      * Appends all of the elements in the specified collection to the end of
      * this list, in the order that they are returned by the
      * specified collection's Iterator.  The behavior of this operation is
      * undefined if the specified collection is modified while the operation
      * is in progress.  (This implies that the behavior of this call is
      * undefined if the specified collection is this list, and this
      * list is nonempty.)
      *
      * @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) {
     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 ;
     }

 

Collection 接口定义了toArray方法,如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
     /**
      * Returns an array containing all of the elements in this collection.
      * If this collection makes any guarantees as to what order its elements
      * are returned by its iterator, this method must return the elements in
      * the same order.
      *
      * <p>The returned array will be "safe" in that no references to it are
      * maintained by this collection.  (In other words, this method must
      * allocate a new array even if this collection is backed by an array).
      * The caller is thus free to modify the returned array.
      *
      * <p>This method acts as bridge between array-based and collection-based
      * APIs.
      *
      * @return an array containing all of the elements in this collection
      */
     Object[] toArray();

 

下面是arraylist对其的一种实现:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    /**
      * Returns an array containing all of the elements in this list
      * in proper sequence (from first to last element).
      *
      * <p>The returned array will be "safe" in that no references to it are
      * maintained by this list.  (In other words, this method must allocate
      * a new array).  The caller is thus free to modify the returned array.
      *
      * <p>This method acts as bridge between array-based and collection-based
      * APIs.
      *
      * @return an array containing all of the elements in this list in
      *         proper sequence
      */
     public  Object[] toArray() {
         return  Arrays.copyOf(elementData, size);
     }

 

备注:

/**

 * This class contains various methods for manipulating arrays (such as

 * sorting and searching).  This class also contains a static factory

 * that allows arrays to be viewed as lists.

 

2.4 删除

一种是按索引删除,不用查询,索引之后的element顺序左移一位,并将最后一个element设为null,由gc负责回收。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   /**
      * 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 = (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;
     }

 

 

 

 

3. Arrays.copyOf 

源码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     /**
      * Copies the specified array, truncating or padding with nulls (if necessary)
      * so the copy has the specified length.  For all indices that are
      * valid in both the original array and the copy, the two arrays will
      * contain identical values.  For any indices that are valid in the
      * copy but not the original, the copy will contain <tt>null</tt>.
      * Such indices will exist if and only if the specified length
      * is greater than that of the original array.
      * The resulting array is of the class <tt>newType</tt>.
      *
      * @param original the array to be copied
      * @param newLength the length of the copy to be returned
      * @param newType the class of the copy to be returned
      * @return a copy of the original array, truncated or padded with nulls
      *     to obtain the specified length
      * @throws NegativeArraySizeException if <tt>newLength</tt> is negative
      * @throws NullPointerException if <tt>original</tt> is null
      * @throws ArrayStoreException if an element copied from
      *     <tt>original</tt> is not of a runtime type that can be stored in
      *     an array of class <tt>newType</tt>
      * @since 1.6
      */
     public  static  <T,U> T[] copyOf(U[] original,  int  newLength, Class<?  extends  T[]> newType) {
         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;
     }

 

此处jdk有所优化,如果是object类型,直接new object数组,如果不是通过Array.newInstance调用native方法产生响应的数组类型,创建数组后,通过System.arrayCopy实现数组复制,System是个final类,copyof是个native方法。(如果实现,为何用native方法???)源码如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    @param       src      the source array.
      @param       srcPos   starting position in the source array.
      @param       dest     the destination array.
      @param       destPos  starting position in the destination data.
      @param       length   the number of array elements to be copied.
      @exception   IndexOutOfBoundsException   if  copying would cause
      *               access of data outside array bounds.
      @exception   ArrayStoreException   if  an element in the <code>src</code>
      *               array could not be stored into the <code>dest</code> array
      *               because of a type mismatch.
      @exception   NullPointerException  if  either <code>src</code> or
      *               <code>dest</code> is <code> null </code>.
      */
     public  static  native  void  arraycopy(Object src,   int   srcPos,
                                         Object dest,  int  destPos,
                                         int  length);

 

备注:

  1. Native修饰符标示该方法的实现体非java,而是c++或者其他语言

  2.   有助于提升性能

 

4. 关于native

Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。

  可以将native方法比作Java程序同C程序的接口,其实现步骤:
  1、在Java中声明native()方法,然后编译;
  2、用javah产生一个.h文件;
  3、写一个.cpp文件实现native导出方法,其中需要包含第二步产生的.h文件(注意其中又包含了JDK带的jni.h文件);
  4、将第三步的.cpp文件编译成动态链接库文件;
  5、在Java中用System.loadLibrary()方法加载第四步产生的动态链接库文件,这个native()方法就可以在Java中被访问了。

        上述例子中,System.arrayCopy()和Array.newInsance(compnentType, length)均采用native方法,补充Array.newInstance()源码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
      @param  componentType the <code>Class</code> object representing the
      * component type of the  new  array
      @param  length the length of the  new  array
      @return  the  new  array
      @exception  NullPointerException  if  the specified
      * <code>componentType</code> parameter is  null
      @exception  IllegalArgumentException  if  componentType is { @link  Void#TYPE}
      @exception  NegativeArraySizeException  if  the specified <code>length</code> 
      * is negative
      */
     public  static  Object newInstance(Class<?> componentType,  int  length)
     throws  NegativeArraySizeException {
     return  newArray(componentType, length);
     }

 

   

?
1
2
     private  static  native  Object newArray(Class componentType,  int  length)
     throws  NegativeArraySizeException;

 

5 Array.newInstance()的意义

利用Array.newInstance()创建数组的意义是什么?

Java反射技术除了可以在运行时动态地决定要创建什么类型的对象,访问哪些成员变量,方法,还可以动态地创建各种不同类型,不同维度的数组。

动态创建数组的步骤如下:
1.创建Class对象,通过forName(String)方法指定数组元素的类型
2.调用Array.newInstance(Class, length_of_array)动态创建数组

访问动态数组元素的方法和通常有所不同,它的格式如下所示,注意该方法返回的是一个Object对象
Array.get(arrayObject, index)

为动态数组元素赋值的方法也和通常的不同,它的格式如下所示, 注意最后的一个参数必须是Object类型
Array.set(arrayObject, index, object)

动态数组Array不单可以创建一维数组,还可以创建多维数组。步骤如下:
1.定义一个整形数组:例如int[] dims= new int{5, 10, 15};指定一个三维数组
2.调用Array.newInstance(Class, dims);创建指定维数的数组

访问多维动态数组的方法和访问一维数组的方式没有什么大的不同,只不过要分多次来获取,每次取出的都是一个Object,直至最后一次,赋值也一样。

动态数组Array可以转化为普通的数组,例如:
Array arry = Array.newInstance(Integer.TYPE,5);
int arrayCast[] = (int[])array;

?
1
2
3
4
5
6
7
8
9
10
public  static  void  main(String args[])  throws  Exception {
Class<?> classType = Class.forName( "java.lang.String" );
// 创建一个长度为10的字符串数组
Object array = Array.newInstance(classType,  10 );
// 把索引位置为5的元素设为"hello"
Array.set(array,  5 "hello" );
// 获得索引位置为5的元素的值
String s = (String) Array.get(array,  5 );
System.out.println(s);
}

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public  static  void  main(String args[]) {
int [] dims =  new  int [] {  5 10 15  };
// 创建一个具有指定的组件类型和维度的新数组。
Object array = Array.newInstance(Integer.TYPE, dims);
 
// 取出三维数组的第3行,为一个数组
Object arrayObj = Array.get(array,  3 );
Class<?> cls = arrayObj.getClass().getComponentType();
System.out.println(cls);
 
// 取出第3行的第5列,为一个数组
arrayObj = Array.get(arrayObj,  5 );
// 访问第3行第5列的第10个元素,为其赋值37
Array.setInt(arrayObj,  10 37 );
 
// 动态数组和普通数组的转换:强行转换成对等的数组
int  arrayCast[][][] = ( int [][][]) array;
System.out.println(arrayCast[ 3 ][ 5 ][ 10 ]);
}

 

6  为什么elementData 要声明为transient变量

?
1
2
3
4
5
6
7
8
9
10
public  class  ArrayList<E>  extends  AbstractList<E>
         implements  List<E>, RandomAccess, Cloneable, java.io.Serializable
{
     private  static  final  long  serialVersionUID = 8683452581122892189L;
 
     /**
      * 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;

 

序列化有2种方式: 

A、只是实现了Serializable接口。 

  序列化时,调用java.io.ObjectOutputStream的defaultWriteObject方法,将对象序列化。 

注意:此时transient修饰的字段,不会被序列化。 

B、实现了Serializable接口,同时提供了writeObject方法。 

  序列化时,会调用该类的writeObject方法。而不是java.io.ObjectOutputStream的defaultWriteObject方法。 

注意:此时transient修饰的字段,是否会被序列化,取决于writeObject

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
     /**
      * 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{
     // Write out element count, and any hidden stuff
     int  expectedModCount = modCount;
     s.defaultWriteObject();
 
         // Write out array length
         s.writeInt(elementData.length);
 
     // Write out all elements in the proper order.
     for  ( int  i= 0 ; i<size; i++)
             s.writeObject(elementData[i]);
 
     if  (modCount != expectedModCount) {
             throw  new  ConcurrentModificationException();
         }
 
     }

 

  ArrayList是会开辟多余空间来保存数据的,而系列化和反序列化这些没有存放数据的空间是要消耗更多资源的,所以ArrayList的数组就声明为transient,自己实现write/readObject方法,仅仅系列化已经存放的数据

 

7 为何要序列化,不是在内存中吗

ArrayList 实现了java.io.Serializable接口,在需要序列化的情况下,复写writeObjcet和readObject方法提供适合自己的序列化方法。

1、序列化是干什么的?

  简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。

2、什么情况下需要序列化  

    a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;

    b)当你想用套接字在网络上传送对象的时候;

    c)当你想通过RMI传输对象的时候;

3.   示例代码

1.import java.io.*;

3.public class  Box  implements  Serializable  //要保存的对象类必须实现序列化接口serializable

4.{ 

5.    private int width; 

6.    private int height; 

7. 

8.    public void setWidth(int width){ 

9.        this.width  = width; 

10.    } 

11.    public void setHeight(int height){ 

12.        this.height = height; 

13.    } 

14.

15.    public static void main(String[] args){ 

16.        Box myBox = new Box(); 

17.        myBox.setWidth(50); 

18.        myBox.setHeight(30); 

19. 

20.        try{  //序列化。

21.            FileOutputStream fs = new FileOutputStream("foo.ser"); 

22.            ObjectOutputStream os =  new ObjectOutputStream(fs); 

23.            os.writeObject(myBox); 

24.            os.close(); 

25.        }catch(Exception ex){ 

26.            ex.printStackTrace(); 

27.        } 

28.    }     

30.} 

 

 发序列化方法

Public static void seserialize(string filename) throws Exception

{

           // 反序列化(读出保存的对象文件)

ObjectInputStream in = new ObjectInputStream (new FileInputStream(filename));

Box box = (Box) (in.readbject());

System.out.println(box.toString());

In.Closer();

}

 

8  总结

1.Arraylist基于数组实现,是自增长的

2,非线程安全的

3.插入时可能要扩容,删除时size不会减少,如果需要,可以使用trimToSize方法,在查询时,遍历查询,为null,判断是否是null, 返回; 如果不是null,用equals判断,返回

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    /**
      * Returns <tt>true</tt> if this list contains the specified element.
      * More formally, returns <tt>true</tt> if and only if this list contains
      * at least one element <tt>e</tt> such that
      * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
      *
      * @param o element whose presence in this list is to be tested
      * @return <tt>true</tt> if this list contains the specified element
      */
     public  boolean  contains(Object o) {
     return  indexOf(o) >=  0 ;
     }
 
     /**
      * 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&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;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 ;
     }

 

4. 允许重复和 null 元素

 

转载于:https://www.cnblogs.com/tutuha/p/4992644.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值