ArrayList & Vector (transient关键字)

ArrayList & Vector (transient)

集合是Java中很是重要并且基础的内容,由于任何数据必不可少的就是数据的存储。集合的做用就是以必定的方式组织、存储数据。下面说说ArrayList,只捡干货聊。java

ArrayList特色

一、ArrayList是基于数组实现的,是一个动态数组,其容量能自动增加,相似于C语言中的动态申请内存,动态增加内存。数组

二、ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下能够考虑用Collections.synchronizedList(List l),函数返回一个线程安全的ArrayList类,底层方法内使用synchronized同步块进行控制;也可使用concurrent并发包下的CopyOnWriteArrayList类,底层数组直接使用关键字volatile。Vector则在操做数组的方法上加上了关键字synchronized。安全

三、ArrayList实现了Serializable接口,所以它支持序列化,可以经过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是经过下标序号进行快速访问,实现了Cloneable接口,能被克隆。多线程

四、每一个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它老是至少等于列表的大小。随着向ArrayList中不断添加元素, 其容量也自动增加。自动增加会带来数据向新数组的从新拷贝,所以,若是可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前, 应用程序也可使用ensureCapacity操做来增长ArrayList实例的容量,这能够减小递增式再分配的数量。 并发

是否容许为空 容许
是否容许重复 容许
是否有序 有序
是否线程安全 非线程安全

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

ArrayList底层

对于ArrayList而言,它实现List接口、底层使用数组保存全部元素。其操做基本上是对数组的操做。下面咱们来分析ArrayList的源代码:函数

   1) 私有属性:性能

   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;

 很容易理解,elementData存储ArrayList内的元素(应该是堆内存中元素的引用,而不是实际的元素 ),size表示它包含的元素的数量。this

有个关键字须要解释:transient。  

Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,咱们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,能够在这个域前加上关键字transient。

有点抽象,看个例子应该能明白。

public class UserInfo implements Serializable {  
     private static final long serialVersionUID = 996890129747019948L;  
     private String name;  
     private transient String psw;  
   
     public UserInfo(String name, String psw) {  
         this.name = name;  
         this.psw = psw;  
     }  
   
     public String toString() {  
         return "name=" + name + ", psw=" + psw;  
     }  
 }  
   
 public class TestTransient {  
     public static void main(String[] args) {  
         UserInfo userInfo = new UserInfo("张三", "123456");  
         System.out.println(userInfo);  
         try {  
             // 序列化,被设置为transient的属性没有被序列化  
             ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(  
                     "UserInfo.out"));  
             o.writeObject(userInfo);  
             o.close();  
         } catch (Exception e) {  
             // TODO: handle exception  
             e.printStackTrace();  
         }  
         try {  
             // 从新读取内容  
             ObjectInputStream in = new ObjectInputStream(new FileInputStream(  
                     "UserInfo.out"));  
             UserInfo readUserInfo = (UserInfo) in.readObject();  
             //读取后psw的内容为null  
             System.out.println(readUserInfo.toString());  
         } catch (Exception e) {  
             // TODO: handle exception  
             e.printStackTrace();  
         }  
     }  
 }

被标记为transient的属性在对象被序列化的时候不会被保存。 ntData数组被序列化。这是为何?由于序列化ArrayList的时候,ArrayList里面的elementData未必是满的,比方说elementData有10的大小,可是我只用了其中的3个,那么是否有必要序列化整个elementData呢?显然没有这个必要,所以ArrayList中重写了writeObject方法:

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

每次序列化的时候调用这个方法,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,而后遍历elementData,只序列化那些有的元素,这样:

  • 加快了序列化的速度
  • 减少了序列化以后的文件大小

这种作法也是值得学习、借鉴的一种思路。接着回到ArrayList的分析中......

2) 构造方法: 
   ArrayList提供了三种方式的构造器,能够构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。

// ArrayList带容量大小的构造函数。    
    public ArrayList(int initialCapacity) {    
        super();    
        if (initialCapacity < 0)    
            throw new IllegalArgumentException("Illegal Capacity: "+    
                                               initialCapacity);    
        // 新建一个数组    
        this.elementData = new Object[initialCapacity];    
    }    
   
    // ArrayList无参构造函数。默认容量是10。    
    public ArrayList() {    
        this(10);    
    }    
   
    // 建立一个包含collection的ArrayList    
    public ArrayList(Collection<? extends E> c) {    
        elementData = c.toArray();    
        size = elementData.length;    
        if (elementData.getClass() != Object[].class)    
            elementData = Arrays.copyOf(elementData, size, Object[].class);    
    }

3) 元素存储与扩容:

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)这些添加元素的方法。

20 // 用指定的元素替代此列表中指定位置上的元素,并返回之前位于该位置上的元素。  
21 public E set(int index, E element) {  
22    RangeCheck(index);  
23 
24    E oldValue = (E) elementData[index];  
25    elementData[index] = element;  
26    return oldValue;  
27 }    
28 // 将指定的元素添加到此列表的尾部。  
29 public boolean add(E e) {  
30    ensureCapacity(size + 1);   
31    elementData[size++] = e;  
32    return true;  
33 }    
34 // 将指定的元素插入此列表中的指定位置。  
35 // 若是当前位置有元素,则向右移动当前位于该位置的元素以及全部后续元素(将其索引加1)。  
36 public void add(int index, E element) {  
37    if (index > size || index < 0)  
38       throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);  
39    // 若是数组长度不足,将进行扩容。  
40    ensureCapacity(size+1); // Increments modCount!!  
41    // 将 elementData中从Index位置开始、长度为size-index的元素,  
42    // 拷贝到从下标为index+1位置开始的新的elementData数组中。  
43    // 即将当前位于该位置的元素以及全部后续元素右移一个位置。  
44    System.arraycopy(elementData, index, elementData, index + 1, size - index);  
45    elementData[index] = element;  
46    size++;  
47 }    
48 // 按照指定collection的迭代器所返回的元素顺序,将该collection中的全部元素添加到此列表的尾部。  
49 public boolean addAll(Collection<? extends E> c) {  
50    Object[] a = c.toArray();  
51    int numNew = a.length;  
52    ensureCapacity(size + numNew); // Increments modCount  
53    System.arraycopy(a, 0, elementData, size, numNew);  
54    size += numNew;  
55    return numNew != 0;  
56 }    
57 // 从指定的位置开始,将指定collection中的全部元素插入到此列表中。  
58 public boolean addAll(int index, Collection<? extends E> c) {  
59    if (index > size || index < 0)  
60      throw new IndexOutOfBoundsException(  
61        "Index: " + index + ", Size: " + size);  
62 
63    Object[] a = c.toArray();  
64    int numNew = a.length;  
65    ensureCapacity(size + numNew); // Increments modCount  
66 
67    int numMoved = size - index;  
68    if (numMoved > 0)  
69      System.arraycopy(elementData, index, elementData, index + numNew, numMoved);  
70 
71    System.arraycopy(a, 0, elementData, index, numNew);  
72    size += numNew;  
73    return numNew != 0;  
   }

底层数组的大小不够了怎么办? 答案就是扩容,这也就是为何一直说ArrayList的底层是基于动态数组实现的缘由,动态数组的意思就是指底层的数组大小并非固定的,而是根据添加的元素大小进行一个判断,不够的话就动态扩容,扩容的代码就在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);
    }
 }

看到扩容的时候把元素组大小先乘以3,再除以2,最后加1。可能有些人要问为何?咱们能够想:

  • 若是一次性扩容扩得太大,必然形成内存空间的浪费
  • 若是一次性扩容扩得不够,那么下一次扩容的操做必然比较快地会到来,这会下降程序运行效率,要知道扩容仍是比价耗费性能的一个操做

因此扩容扩多少,是JDK开发人员在时间、空间上作的一个权衡,提供出来的一个比较合理的数值。最后调用到的是Arrays的copyOf方法,将元素组里面的内容复制到新的数组里面去:

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

数组复制拷贝尽可能使用System.arrayCopy或Arrays.copyof()方法,效率更高。

ArrayList还给咱们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它能够经过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相同,节省空间。

4) 元素读取:

// 返回此列表中指定位置上的元素。  
public E get(int index) {  
    RangeCheck(index);  
    return (E) elementData[index];  
}

5) 元素删除:

ArrayList提供了根据下标或者指定对象两种方式的删除功能。 对于ArrayList来讲,这两种删除的方法差很少,都是调用的下面一段代码:

int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                 numMoved);
    elementData[--size] = null; // Let gc do its work

把指定元素后面位置的全部元素,利用System.arraycopy方法总体向前移动一个位置,最后一个位置的元素指定为null,这样让gc能够去回收它。

ArrayList的优缺点

  • 随机访问快。ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,所以查找也就是get的时候很是快
  • 顺序添加快。ArrayList在顺序添加一个元素的时候很是方便,只是往数组里面添加了一个元素而已
  • 删除元素的时候,涉及到一次元素复制,若是要复制的元素不少,那么就会比较耗费性能
  • 插入元素的时候,涉及到一次元素复制,若是要复制的元素不少,那么就会比较耗费性能

ArrayList和Vector区别

  • Vector属于线程安全级别的,可是大多数状况下不使用Vector,由于线程安全须要更大的系统开销。ArrayList须要使用Collections.synchronizedList方法变成一个线程安全的List。
    List<String> synchronizedList = Collections.synchronizedList(list);
        synchronizedList.add("aaa");
        synchronizedList.add("bbb");
        for (int i = 0; i < synchronizedList.size(); i++)
        {
            System.out.println(synchronizedList.get(i));
        }

    Vector是ArrayList的线程安全版本,其实现90%和ArrayList都彻底同样

  • 增加因子不一样。ArrayList在内存不够时默认是扩展50% + 1个,Vector是默认扩展1倍。
  • Vector提供indexOf(obj, start)接口,ArrayList没有。

ArrayList转静态数组toArray

有两个转化为静态数组的toArray方法。

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

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

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

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

 

Fail-Fast机制: 
ArrayList也采用了快速失败的机制,经过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会彻底失败,而不是冒着在未来某个不肯定时间发生任意不肯定行为的风险。具体介绍请参考这篇文章HashMap中的Fail-Fast机制。

相关文章
相关标签/搜索