容器的源码(ArrayList)

ArrayList是基于数组实现的列表,支持快速随机访问。其默认容量为10,提供多种构造方法。添加元素时会根据需要扩容,扩容策略为原容量的1.5倍。ArrayList不是线程安全的,序列化时只序列化填充内容。在多线程环境下需注意其并发问题。
摘要由CSDN通过智能技术生成

容器的源码(ArrayList)

ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList是基于数组实现的,所以支持快速随机访问

数组的默认大小为10

private static final int DEFAULT_CAPACITY = 10;

arraylist提供了三种构造方法。可以使用空参构造,还可以传递一个int值(指定初始容量)或是一个集合

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

transient Object[] elementData;

elementData用来存放实际数据,并使用transient修饰,表明该数据在默认的序列化机制时不会被序列化。arraylist实现了java.io.SeriaLizable接口,arraylist自己实现了序列化与反序列化的方法

DEFAULTCAPACITY_EMPTY_ELEMENTDATA则是一个空的数组。

注意DEFAULTCAPACITY_EMPTY_ELEMENTDATA类型为static final,表明其在内存中只有一份且禁止修改。

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

《阿里巴巴Java开发手册》里面建议初始化集合时尽量显示的指定集合大小。为什么?读了上面的源码之后,应该可以知道答案了。

1.节约内存,实际编码中,很多时候我们都可以知道ArrayList里面会放什么元素以及放多少元素。恰当的设置容器大小可以节约内存。

2.避免扩容产生的性能损耗。

逻辑非常简单,如果初始容量>0,则创建一个该大小的数组。如果容量为0,则创建一个空数组。如果容量<0,抛出异常。

public ArrayList(Collection<? extends E> c) {
  elementData = c.toArray();
  if ((size = elementData.length) != 0) {
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
      elementData = Arrays.copyOf(elementData, size, Object[].class);
  } else {
    // replace with empty array.
    this.elementData = EMPTY_ELEMENTDATA;
  }
}

逻辑并不复杂,直接将集合转换为Object数组,赋值给了elementData属性。注意注释上面声明了可能抛出NullPointerException,看源码会发现当我们传递进来的元素是null值的时候,会在c.toArray()的时候抛出NPE。

添加元素

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

private int size;

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
  modCount++;
  // overflow-conscious code
  if (minCapacity - elementData.length > 0)
    //扩容
    grow(minCapacity);
}

首先,调用了ensureCapacityInternal()方法,入参传递了size+1,size+1表示elementData所需要的最小长度。这里的size变量,是用来记录ArrayList包含元素的多少的,初始值为0,我们调用ArrayList的size()方法,返回的就是该字段。

ensureCapacityInternal方法做了一件事情,判断当前数组能不能方法即将被添加的元素,如果不能,扩容。调用了calculateCapacity()计算容量

调用完calculateCapacity()后,调用ensureExplicitCapacity(),这个方法做了两件事情:

1.将modCount自增

2.如果容量不够,扩容。

扩容的方法grow()

private void grow(int minCapacity) {
   // overflow-conscious code
   int oldCapacity = elementData.length;
   int newCapacity = oldCapacity + (oldCapacity >> 1);
   if (newCapacity - minCapacity < 0)
     newCapacity = minCapacity;
   if (newCapacity - MAX_ARRAY_SIZE > 0)
     newCapacity = hugeCapacity(minCapacity);
   // minCapacity is usually close to size, so this is a win:
   elementData = Arrays.copyOf(elementData, newCapacity);
 }

可以看到扩容后的容量为原容量的1.5倍+1。

rrayList不是无限扩容的,它是有限度的。上面的代码有一个判断:

**if** **(**newCapacity **-** MAX_ARRAY_SIZE **>** 0**)**newCapacity **=** hugeCapacity**(**minCapacity**);**

如果扩容后的容量比数组最大容量大,调用hugeCapacity()方法,并将扩容前所需要的最小容量传递的进去。

private static int hugeCapacity(int minCapacity) {
  if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
  return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

hugeCapacity方法只在扩容时可能被调用,它的逻辑很简单,先做了个简单的判断,之后执行了一个三元表达式,如果扩容前所需最小容量大于数组最大长度,返回Integer的最大值,否则返回MAX_ARRAY_SIZE,MAX_ARRAY_SIZE为Integer的最大值-8。

**private** **static** **final** **int** MAX_ARRAY_SIZE **=** Integer**.**MAX_VALUE **-** 8**;**

int的最大值为2的31次方-1,所以说ArrayList的最大容量为2的31次方-1。

至于空参创造的ArrayList

第一次调用add()方法时发生了什么?扩容,是的,它会将默认的空数组扩容为一个长度为10的数组。

添加到指定.位置add(int index, E element)

注意ArrayList的扩容时机和HashMap有区别,ArrayList只有底层数组已满,不能放下即将存入的对象才会扩容,HashMap的扩容和加载因子有关系,默认情况下,不是容器满了才扩容。

public void add(int index, E element) {
  //检查index是否在已有的数组中
  if (index > size || index < 0)
    throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
  ensureCapacity(size + 1);//确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
  System.arraycopy(elementData, index, elementData, index+1, size-index);//将index及其后边的所有的元素整块后移,空出index位置
  elementData[index] = element;//插入元素
  size++;//已有数组元素个数+1
}

添加所有addAll(Collection<? extends E> c)

public boolean addAll(Collection<? extends E> c) {
  Object[] a = c.toArray();//将c集合转化为对象数组a
  int numNew = a.length;//获取a对象数组的容量
  ensureCapacity(size + numNew);//确保对象数组elementData有足够的容量,可以将新加入的a对象数组加进去
  System.arraycopy(a, 0, elementData, size, numNew);//将对象数组a拷贝到elementData中去
  size += numNew;//重新设置elementData中已加入的元素的个数
  return numNew != 0;//若加入的是空集合则返回false
}

添加所有到指定位置addAll(int index, Collection<? extends E> c)

publicbooleanaddAll(int index, Collection<?extends E> c){
  rangeCheckForAdd(index);

  Object[] a= c.toArray();
		int numNew= a.length;
  ensureCapacityInternal(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;
}

类似的移动

删除元素:

3.1 删除指定索引元素 E remove(int index)。

public E remove(int index) {
  //索引边界检查
  rangeCheck(index);
   //计数器自增
  modCount++;
  //取得被删除元素
  E oldValue = elementData(index);
  //计算要移动的索引值
  int numMoved = size - index - 1;
  if (numMoved > 0)
    //如果删除的不是最后一个元素,进行数组拷贝
    System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
  //将最后一个元素置为null,下次gc回收
  elementData[--size] = null; // clear to let GC do its work
  //返回被删除的值
  return oldValue;
}

ArrayList删除元素的时候可能会进行数据拷贝也是其和LinkedList的区别之一。

3.2 删除指定值的元素 remove(Object o)

public boolean remove(Object o) {
  if (o == null) {//移除对象数组elementData中的第一个null
    for (int index = 0; index < size; index++)
      if (elementData[index] == null) {
        fastRemove(index);
        return true;
      }
  } else {//移除对象数组elementData中的第一个o
    for (int index = 0; index < size; index++)
      if (o.equals(elementData[index])) {
        fastRemove(index);
        return true;
      }
  }
  return false;
}

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; //将最后一个元素设为null,在下次gc的时候就会回收掉了
}

remove(Object o)需要遍历数组,remove(int index)不需要,只需要判断索引符合范围即可,所以,通常:后者效率更高。

4 获取元素

4.1 获取单个元素get(int index)

public E get(int index) {
  rangeCheck(index);//检查索引范围
  return (E) elementData[index];//返回元素,并将Object转型为E
}

private void rangeCheck(int index) {
  if (index >= size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

注意rangeCheck检查的是size的大小,也就是实际存储元素个数,而不是容器的实际容量。

5. 遍历元素 iterator()

public Iterator<E> iterator() {
    return new Itr();
  }
ItrArryList的一个私有内部类,实现了Iterator接口。

private class Itr implements Iterator<E> {
  int cursor = 0;//标记位:标记遍历到哪一个元素
  int expectedModCount = modCount;//标记位:用于判断是否在遍历的过程中,是否发生了add、remove操作

  //检测对象数组是否还有元素
  public boolean hasNext() {
    return cursor != size();//如果cursor==size,说明已经遍历完了,上一次遍历的是最后一个元素
  }

  //获取元素
  public E next() {
    checkForComodification();//检测在遍历的过程中,是否发生了add、remove操作
    try {
      E next = get(cursor++);
      return next;
    } catch (IndexOutOfBoundsException e) {//捕获get(cursor++)方法的IndexOutOfBoundsException
      checkForComodification();
      throw new NoSuchElementException();
    }
  }

  //检测在遍历的过程中,是否发生了add、remove等操作
  final void checkForComodification() {
    if (modCount != expectedModCount)//发生了add、remove操作,这个我们可以查看add等的源代码,发现会出现modCount++
      throw new ConcurrentModificationException();
  }

 @Override
 @SuppressWarnings("unchecked")
 public void forEachRemaining(Consumer<? super E> consumer) {
   Objects.requireNonNull(consumer);
   final int size = ArrayList.this.size;
   int i = cursor;
   if (i >= size) {
     return;
   }
   final Object[] elementData = ArrayList.this.elementData;
   if (i >= elementData.length) {
     throw new ConcurrentModificationException();
   }
   while (i != size && modCount == expectedModCount) {
     consumer.accept((E) elementData[i++]);
   }
   // update once at end of iteration to reduce heap write traffic
   cursor = i;
   lastRet = i - 1;
   checkForComodification();
 }
}

需要注意的是这里有一个Java集合的fail-fast事件。你可能已经注意到,我们在调用add()、remove()这些修改集合的方法时,都会修改一个属性modCount。而我们在遍历集合时,首先会保存一份modCount,然后在遍历时,将保存的modCount和成员变量modCount对比,如果不一样,说明被集合已经被修改,抛出ConcurrentModificationException,产生fail-fast事件。

序列化

ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}
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 size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

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

序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。

ArrayList内部还维护了一个size属性,它是用来记录数组中的实际元素个数。

size,modCount,elementData这些成员变量,都注定了ArrayList线程不安全。

.ArrayList实现了Iterator接口,这表明遍历ArrayList使用普通for循环比使用foreach更快

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值