ArrayList详解

来源:互联网 发布:imap ssl 端口 编辑:程序博客网 时间:2024/06/04 23:09
ArrayList是我们使用的最常用的集合,下面我会从ArrayList特征和结构、源码的分析,以及自我实现ArrayList三个方面剖析ArrayList.

ArrayList特征和结构

    ArrayList是java中的动态数组。它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。
ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
和Vector不同,ArrayList中的操作不是线程安全的。所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。

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中一切操作都是对elementData数组的操作,而size属性代表ArrayList中元素的个数。
我们看看ArrayList的基本的增、删、改、查操作:
public boolean add(E e);public void add(int index, E element);public E set(int index, E element);public E remove(int index);public boolean remove(Object o);public int indexOf(Object o);
具体操作我就不写在上面了,我主要讨论一下里面涉及到的问题和原理.在add()操作过程中,会先判断当前数组容量是否足够插入数据.
扩充容量方法如下:
    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);}
    通过源码,我们可以看到,List集合的容量扩展是按以前容量的1.5倍增长的。add(index,e)的具体过程是怎么样的,它是数组的一种插入数据的操作.set(index,e)是将数组index下标的值设为e,它的返回值是原来index的旧值.这些基本的知识点,都需要我们通过查看ArrayList的源码去看的。别人说的再多作用也不大,当我们自己去查看ArrayList的源码时,就会有很多意想不到的收获的!

数组的复制操作

    对ArrayList的操作就是对数组的操作,所以在ArrayList中有很多对数组的复制内容。接下来我们就一起来学习数组的复制.
数组的复制有两种方法:
(1)使用System.arraycopy()复制
eg:
int a[]={1,2,3,4,5,6,7};int b[]=new int[5];System.arraycopy(a,2,b,1,3);//参数的意义:a为原数组,2为a中开始复制的位置,b为目标数组,1为b中开始的位置,3为复制的长度//所以b中的值为: 0,3,4,5,0int a[]=new int[]{1,2,3,4,5,6,7};int b[]=new int[2];System.arraycopy(a,3,b,0,Math.min(a.length-3,b.length-0));  System.out.println(Arrays.toString(b)); 
src - 源数组。
srcPos - 源数组中的起始位置。
dest - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量。
(2)使用Arrays.copyOf()或Arrays.copyOfRange()方法
常用API如下:
    public static boolean[] copyOf(boolean[] original, int newLength);   public static byte[] copyOf(byte[] original, int newLength);   public static int[] copyOf(int[] original, int newLength);   public static short[] copyOf(short[] original, int newLength);   public static long[] copyOf(long[] original, int newLength);   public static float[] copyOf(flot[] original, int newLength);   public static double[] copyOf(double[] original, int newLength);   public static  T[] copyOf(T[] original, int newLength);   public static  T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType);  
可以看到对基本数据类型都有对应的copyof方法,同时也有引用类型的复制 T[] copyOf,其内部都是调用System.arrayCopy()方法实现的.如果需要数组的复制范围选择,可以选择Arrays.copyOfRange()方法。
我们着重来看看 public static T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType); 方法。T,U肯定是两个不同的类型,那么又能实现从U数组的内容复制到T数组,那么能说明什么呢?
我们查看源码:
     public static  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;    }  
里面先通过 Array.newInstance(),反射创建一个与newType类型相同的数组,然后调用 system.arraycopy方法,实现数组的复制,那么能够从U类型数组复制元素到T类型元素,那么可以确定的是,T类型一定能兼容U类型,比如说向上转型,或者 Object o=new Integer(12) 我们通过例子说明:
Integer[] intArray = new Integer[] { 1, 2, 3, 4, 5 };Object obj2[] = copyOf(intArray, 4, Object[].class);for (Object obj : obj2) {System.out.println(obj);}  

ArrayList数组应用再探

上面一部分都是数组工具类Arrays中常用的一些功能,其复制功能在ArrayList中得到了广泛的应用.接下来,我们再来看看ArrayList中的另外两个高级应用:
(1)ArrayList转换成数组
相应API如下:
  public Object[] toArray();public <t> T[] toArray(T[] a);</t>
第一个方法没什么好说的,我们看第二个方法。传入T[]a,然后返回 T[]类型数组,这是个什么鬼!
还是看它的源代码吧:
        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;        }</t>
分析结论如下:
    当数组a的长度小于size,则创建新的数组,复制ArrayList中数组的内容并返回新数组;当数组a的长度等于size时,复制ArrayList中数组的内容到数组a中,并返回a;当数组a的长度大于size时,复制ArrayList中数组的内容到数组a中,设置a[size]=null,并返回a 大家有没有感觉一脸懵逼的感觉啊,为啥要这样设计咧!反正我感觉,直接传入T.class参数蛮好的:
     @SuppressWarnings("unchecked")public <t> T[] toArray(Class<? extends T> cls) {T newType[] = (T[]) Array.newInstance(cls, size);System.arraycopy(elementData, 0, newType, 0, size);return newType;}</t>
(2)ArrayList的迭代器操作
在AbstractList中通过内部类实现Iterator接口,实现迭代,这里应用到了迭代器模式,大家可以通过查看源码来体会这种设计.以及实现ListIterator接口,实现List的前后双向迭代过程。

自己实现ArrayList

研究ArrayList源码,我们才能更清楚透彻的去了解ArrayList结构和实现。以前总是听别人说集合里面的代码特别的优秀,很值得我们阅读和借鉴。总是没时间,也不敢去尝试,终于花了一天时间来分析和了解ArrayList,并自己模仿写出自己的ArrayList,虽然有很多东西还是不知道为啥这样做,但是还是收获满满的,所以鼓励大家多看看源码,在这里我也无耻的粘上自己的源码咧!
自定义ArrayList下载
最后欢迎大家和我一起讨论学习,一起提高!
0 0
原创粉丝点击