java.util.ArrayList

本文详细解析了Java中的ArrayList类,包括transient关键字的使用、modCount变量的功能、构造函数、主要方法如trimToSize和ensureCapacity的工作原理,以及如何通过writeObject和readObject实现序列化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java.util.ArrayList.java

 

疑问:

  1. transient

    在这里类中发现了以前没有见过的transient关键字。这个关键字的主要标示这个属性不需要自动序列化。因为ArrayList实现了java.io.Serializable接口,所以ArrayList对象可以序列化到持久存储介质中。在这个类中,serialVersionUIDsize都将自动序列化到介质中,但elementData数组对象却定义为transient了。也就是说 ArrayList中的所有这些元素都不会自动系列化到介质中。为什么要这样实现?因为elementData数组中存储的“元素”其实仅是对这些元素的 一个引用,并不是真正的对象,序列化一个对象的引用是毫无意义的,因为序列化是为了反序列化,当你反序列化时,这些对象的引用已经不可能指向原来的对象 了。所以在这儿需要手工的对ArrayList的元素进行序列化操作。这就是writeObject()的作用。

  2. modCount

    在这个类中,有很多地方出现了modCount这个变量。这个变量是在该类的超类AbstractArrayList中定义的。它是用来记录了ArrayList结构性变化的次数的。那么这个结构性变化的次数有什么用呢?还是在AbstractArrayList中,可以利用iterator()方法生成一个实现了Iterator接口的Itr对象。因为这个Itr类是AbstractArrayList的一个私有内部类,所以这个类可以自由的访问AbstractArrayList的属性和方法。Itr对象在初始化的时候,初始化了其内部的一个属性:int expectedModCount = modCount;当我们遍历这个对象内部的属性的时候,需要比较这 expectedModCountmodCount的值是否相等,以确保我们遍历的是同一个对象。这个可能在多线程的编程中非常有用。假如我们在一个线程中遍历这个集合,而在遍历的过程中另外的一个线程修改了集合的结构,这样就会抛出ConcurrentModificationException异常。

 

构造函数:

ArrayList一共三个构造函数:

public ArrayList(int initialCapacity)
{
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    this.elementData = new Object[initialCapacity];
}

根据给定数值initialCapacity初始化initialCapacity大小的ArrayList

public ArrayList()
{
    this(10);
}

默认初始化长度为10ArrayList

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

由已存在的Collection构造并初始化一个新的ArrayList

 

主要方法:

  1. public void trimToSize()

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

释放多余的空间。如果存入的元素大小小于分配的空间大小,则将释放掉空余的空间。该方法会引起ArrayList实例结构改变,因此modCount需要加1

 

  1. 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);
        }
    }

增加ArrayList实例的容量。参数minCapacity是实例扩容时所必须保证的最小值。从程序上来看,每次的扩容实际上增加的是原先实例容量的1.5倍再加1。该方法会引起ArrayList实例结构的改变,所以modCount自加1

 

  1.  

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

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

 

返回元素在结构中的位置。从程序中可以看出来indexOf返回的是该元素第一次出现的位置,而lastIndexOf则返回该元素在实例中最后一次出现的位置。如果给定的求解元素为null的话,也会得到第一个或者最后一个为null的位置。

 

  1. public Object clone()
    {
        try
        {
              ArrayList<E> v = (ArrayList<E>) super.clone();
              v.elementData = Arrays.copyOf(elementData, size);
              v.modCount = 0;
              return v;
        } catch (CloneNotSupportedException e)
        {
             // this shouldn't happen, since we are Cloneable
             throw new InternalError();
        }

    }

方法名顾名思义,克隆现有的ArrayList实例。但是这里需要注意的是对实力元素的copy是一种浅copy,也就是说新克隆出的ArrayList实例中的元素和原实例中的元素是同一个元素,是共享的,改变一个就会改变另外一个。因此使用该方法的时候要加以注意。]

 

  1. 增加元素

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

    增加一个元素,在增加前先对容量进行修改,在这里会修改实例的结构,因此modCount会加1,然后将增加的元素加入到实例的末尾。

     

    public void add(int index, E element)

    {

    1. 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++;

    }

    在指定位置增加元素。和上面的方法一样,首先对现有容量进行修改,同样也会修改实例的结构,modCount会加1。然后将指定位置之后的所有元素向后顺移一位,在将指定元素插入指定位置。这里注意,如果指定的位置超过了实例的上界和下界将会抛出IndexOutOfBoundsException异常。

     

    public boolean addAll(Collection<? extends E> c)

    {

    1. 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接口的类实例中的元素加入到现有的实例中。该操作会影响实例的结构,所以modCount自加1。调用这个方法需要注意一个问题,该操作执行的过程中不会注意的Collection是否已经被修改了。

     

    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;

    }

    这个方法和上一个方法差不多,只不过是在指定的位置插入一个实现了Collection接口的类的实例。插入之前,将该指定位置的元素挨个向后移动一位。同样,该操作会引起实例结构的改变。在执行操作之前,会判断指定的位置是否合法。

     

  2. 删除元素

    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;

    }

    移除指定位置上的元素并且返回移除的元素。这个方法会先调用一个叫做RangeCheck的方法,这个方法用来检查给定的位置是否合法,如果非法,会抛出IndexOutOfBoundsException。该方法会改变实例的结构。移除之后,该位置之后的元素会向前顺移一位。源码中有一个很有意思的注释:Let gc do its work !这里可能会产生内存泄露的问题。

     

    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;

    }

    这个方法是从实例中第0个元素开始查找指定的元素,如果查找到则移除指定的元素成功,否则失败。注意指定元素为null也可以被移除。在移除元素的时候,该方法将找到的指定元素的位置传递给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

    }

    该方法和public E remove(int index)方法的实现方式是一样的,只不过该方法不会返回被删除的元素。

     

    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;

    }

    移除指定位置之间的所有元素(前闭后开)。该方法会引起结构变动。

     

    public void clear()

    {

    modCount++;

    // Let gc do its work

    for (int i = 0; i < size; i++)

    elementData[i] = null;

    size = 0;

    }

    清除实例中的所有元素。该操作会引起实例结构的变化。

     

  3.  

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

}

}

private void readObject(java.io.

ObjectInputStream s)

throws java.io.IOException, ClassNotFoundException

{

// Read in size, and any hidden stuff

s.defaultReadObject();

// Read in array length and allocate

//array

int arrayLength = s.readInt();

Object[] a = elementData = new

Object[arrayLength];

// Read in all elements in the proper

//order.

for (int i=0; i<size; i++)

a[i] = s.readObject();

}

 

因为ArrayList实现了java.io.Serializable接口,因此该类可以被序列化。这两个方法就是序列化的写和读方法。在写方法中需要注意一点,在真正执行写之前,方法保存了modCountcopy,在写完之后会用该copymodCount进行比较。如果不一致则抛出 ConcurrentModificationException异常。这是非常有意思的一点。该过程保证了待写元素在写的过程中没有被更改。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值