jdk1.6ArrayList底层实现

转载自五月的仓颉博客地址:http://www.cnblogs.com/xrq730/p/4989451.html

对于集合需要关注四点

1. 是否允许为空

2. 是否允许重复

3. 是否有序(取出元素的顺序是否和插入的顺序一致)

4. 是否是线程安全的

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;

elementData: 就是ArrayList集合底层的数组

size:ArrayList中元素的个数,size按照调用add,remove的次数自增或者自减,就是add null也会去自增1

ArrayList的特性

 

关注点结论
ArrayList是否允许为空允许
ArrayList是否允许重复允许
ArrayList是否有序有序
是否线程安全线程不安全

构造函数

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

  
    public ArrayList() {
	this(10);
    }

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

实例化集合的时候,可以指定集合的容量(就是底层数组的容量),不指定容量大小默认size=10

也能传入一个集合作为初始化参数,此时,会将集合转为数组,并通过Arrays.copyOf生成新的数组,将引用指向这个新的数组

添加元素和扩容

public boolean add(E e) {
	ensureCapacity(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
    }
	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进行一次扩容检查,如果当前元素个数大于ArrayList的容量大小就会进行一次扩容,这也就是说ArrayList的底层是基于动态数组实现的原因,具体来说:将新的数组的容量扩容为之前的1.5倍的加1(jdk1.7中改为1.5倍扩容),在调用Arrays.copyOf将原来数组的元素,复制到新的数组,并将elementData引用指向它. 这个过程中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;
    }

可能有人会问,为什么要这样扩容

1、如果一次性扩容扩得太大,必然造成内存空间的浪费
2、如果一次性扩容扩得不够,那么下一次扩容的操作必然比较快地会到来,这会降低程序运行效率,要知道扩容还是比价耗费性能的一个操作
所以扩容扩多少,是JDK开发人员在时间、空间上做的一个权衡,提供出来的一个比较合理的数值。

删除元素

1. 按照元素的下标删除

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

 

2. 按照元素删除,删除首次出现的这个元素

 

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

 

对于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

1. 将index+1开始的元素,向前移动一个位置

2. 将最后一个元素指定为null,可以让gc去回收它

插入元素

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

和直接add元素类似,也会先调用一次扩容方法,然后,将index开始的元素整体向后移动一位,最后在index位置存放元素地址

ArrayList fail-fast策略

ArrayList也采用了快速失败的策略,通过记录modCount(每次去修改ArrayList结构都会去修改这个值)来实现。在并发的情况下,使用迭代器迭代,会检查modCount和expectedModCount是否相等,如果不相等,抛出异常。

final void checkForComodification() {
	    if (modCount != expectedModCount)
		throw new ConcurrentModificationException();
	}
    }

 

ArrayList的优缺点

 

1. ArrayList底层是数组实现的,是一种随机访问模式,加上它实现了RandomAccess接口,因而查询元素很快

2. ArrayList在顺序添加一个元素,并且添加元素的个数不大于ArrayList的容量大小,这个操作只是会将元素的位置存放在数组的索引位置,不会耗费什么资源,会很快。(也就是如果能够事先指定ArrayList的容量大小,顺序添加元素很快)

3. ArrayList在删除元素和插入元素的表现较差,如果元素很多,移动元素会很耗费资源

结论: ArrayList适合查询元素和在指定容量的前提下,顺序添加元素

ArrayList和Vector的区别

从ArrayList操作元素的方法可以看出,不是同步的,在多线程环境下,一定会出现线程安全问题,如果想要使用ArrayList又想要他线程安全怎么办?

一个方法是使用Collections.synchronizedList(List) 生成一个线程安全的list

public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }

Vector 是ArrayList线程安全的版本,它的实现90%和ArrayList一样,区别

1. 它是线程安全的,方法是同步的

2. 构造时候能够指定增长因子capacityIncrement,如果不指定,扩容按照原来的两倍扩容

 private void ensureCapacityHelper(int minCapacity) {
	int oldCapacity = elementData.length;
	if (minCapacity > oldCapacity) {
	    Object[] oldData = elementData;
	    int newCapacity = (capacityIncrement > 0) ?
		(oldCapacity + capacityIncrement) : (oldCapacity * 2);
    	    if (newCapacity < minCapacity) {
		newCapacity = minCapacity;
	    }
            elementData = Arrays.copyOf(elementData, newCapacity);
	}
    }

另外,jdk1.7中提供一个ensureCapacity公开的方法,手动扩容,这个方法在添加大数据量前调用,能够提高效率。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值