对于ArrayList的理解- Java集合篇

ArrayList的本质还是数组,是Java一种可变长数组的数据结构,概括的说底层实现机制,是扩容和拷贝元素。ArrayList实现了List等好多个接口 ,主要看一下List接口。

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

List接口里面提供了一系列的基本方法,都是对一个存储结构的基本方法,增删改查方法。罗列一些

int size();
boolean isEmpty();
boolean contains(Object o);
boolean add(E e);
E remove(int index);
void clear();

ArrayList构造方法有带参数和不带参数构造,带参构造可以指定数组长度

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

无参构造的时候默认数组长度为10。

  public ArrayList() {
	this(10); //这里调用带参数构造方法
    }

除此之外带参数构造的还有传递一个集合类的元素构造,比如构造一个ArrayList的时候可以传递一个ArrayList

	public static void main(String[] args) {
		ArrayList<Integer> ar_list = new ArrayList<Integer>();
		ar_list.add(2);
		ar_list.add(4);
		ar_list.add(5);
		ar_list.add(6);
		ArrayList<Integer> ar_list2= new ArrayList<Integer>(ar_list);
		
		System.out.println(ar_list);
		System.out.println(ar_list2);
	}
public ArrayList(Collection<? extends E> c) {
	elementData = c.toArray(); //将传递进来的集合类转为数组 赋值给arraylist里面的数组
	size = elementData.length;
	// c.toArray might (incorrectly) not return Object[] (see 6260652)
	if (elementData.getClass() != Object[].class)  //这里的if判断的所用在于 如果传递进来的集合类 转为数组 类型可能部位object 进行进一步的复制赋值
	    elementData = Arrays.copyOf(elementData, size, Object[].class);
    }


存放在ArrayList元素放在一个Object类型的数组里面,这个数组被transient关键字修饰,防止修饰的东西不被序列化,ArrayList是实现了Serializable接口的,一个对象只要实现了Serilizable接口,这个对象就可以被序列化,一个类有的属性不需要被序列化,就加上transient关键词。

  /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     *add元素时候真正存放数据的地方/
    private transient Object[] elementData;

1、trimToSize()方法

arrayList里面不是有size。size代表的数组的存放元素个数,并不是真正数组的长度,数组长度是elementData.length,这个方法的作用在于把arrayList里面的数组容量修剪为列表的当前大小,但是元素还是保留不变的,可能数组长度为10但是里面只有4个元素,通过这个方法可以修剪这个数组,变为长度为4,释放掉了数组中空闲的地方。

 public void trimToSize() {
	modCount++;   //这是从继承那里来的元素记录操作次数
	int oldCapacity = elementData.length;
	if (size < oldCapacity) {
            elementData = Arrays.copyOf(elementData, size);  //将数组的大小改为当前数组存放元素个数
	}
    }

2、ensureCapacity()扩容的方法

这个方法是在往数组添加元素之前调用,判断当前的数组是否还有空余空间存放要添加的元素,

  public void ensureCapacity(int minCapacity) {
	modCount++;  //这里也有
	int oldCapacity = elementData.length; //得到当前数组的长度 也就是最大容量
	if (minCapacity > oldCapacity) {   //传递进来的minCapacity是添加一个元素之后的size 也就是size+1,判断加一个元素之后是否超过了数组长度 
	    Object oldData[] = elementData; //保存一下原来数组所有的元素 好像没啥用
	    int newCapacity = (oldCapacity * 3)/2 + 1; //对于新的数组长度是原来长度的1.5倍 (oldCapacity * 3)/2 可能会向下取整 比如 3 * 3  / 2 =4 所以后面再加一个
    	    if (newCapacity < minCapacity) //放大之后的长度还小于mincapactity 的话 就是这个oldcapacity放大之后超过了int上限了 成了负数了
		newCapacity = minCapacity; //那就不放大了 保持原有长度 已是最大的长度了
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity); //拷贝元素 再copyof方法里面会对elementdata扩容 长度就是newCapacity
	}
    }

3、add()方法

在添加一个元素之前会调用ensureCapacity方法判断剩余数组空间够不够,需不需要扩容,要保证新添加的元素要有地方存放。

 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
	ensureCapacity(size + 1);  // 传递进去的就是size + 1也就是oldcapacity
	elementData[size++] = e;
	return true;
    }

add( )方法还有带双参数的添加元素,在指定index的位置插入元素,看这个源码之前先看一下arraycopy方法,这是System类下一个静态方法

 public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
src:源数组;	srcPos:源数组要复制的起始位置;
dest:目的数组;	destPos:目的数组放置的起始位置;	length:复制的长度。

在指定位置插入元素,这个index一定是在0-size之间插入的,否则也会报下标越界异常。

 public void add(int index, E element) {
	if (index > size || index < 0)
	    throw new IndexOutOfBoundsException(
		"Index: "+index+", Size: "+size);

	ensureCapacity(size+1);  // 检查是够需要扩容
	System.arraycopy(elementData, index, elementData, index + 1,
			 size - index);   //完成拷贝数组 实际上给是index后面的元素后移一位 空出来index的位置
	elementData[index] = element;  //index位置赋值
	size++;
    }

4.indexOf()和contains()方法

主要看一下indexOf( )方法,contains( )方法也是通过调用indexOf( )方法实现的。

再数组里面找!找到第一出现指定元素o的位置,返回下标,没有的话就返回-1。不难理解。还有和indexOf方法类似lastIndexOf方法,区别就是返回最后一次指定元素出现的下标,就是从数组后面开始遍历查找。

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

而contains( )方法就是单纯查看arrayList也就是数组elementdata里面有没有一个指定的元素,直接调用indexOf方法,返回是一个下标,下标小于0的话也就是-1说明就是不存在该元素的。

   public boolean contains(Object o) {
	return indexOf(o) >= 0;
    }

5、RangeCheck( )方法

在调用get()方法之前调用RangeCheck( )检查你传递进来的index是否超过了size,超过了size会抛出一个下标越界的异常。

严格的说并没有下标越界,超过了数组length才算。但是去访问超过了size的内容也不合适。
public E get(int index) {
	RangeCheck(index);

	return (E) elementData[index]; //得到指定下标index处的元素
    }
private void RangeCheck(int index) {
	if (index >= size)
	    throw new IndexOutOfBoundsException(
		"Index: "+index+", Size: "+size);
    }
6、remove()方法

remove()方法有重载,有移除指定下标的元素,有移除指定的元素,看完源码后,移除类的方法都是通过数组拷贝完成的arraycopy方法来完成的。

看一个最简单的移除指定位置元素的方法fastRemove()。

 private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1; //计算index后面还有几个元素 多算了一个 后面--size会用上
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved); //从index+1位置开始拷贝到index位置 拷贝元素个数 上面计算好了
        elementData[--size] = null; // Let gc do its work 让gc做了回收的事情 
    }

移除指定的元素,移除的是第一次出现的元素

    public boolean remove(Object o) {
	if (o == null) {
            for (int index = 0; index < size; index++)//找到这个元素出现的位置index 为啥不直接用indexOf函数
		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; //说明不存在要移除的元素
    }
这个移除元素的方法和fastRemove( )相似,不过这个移除完了之后返回了移除元素,而astRemove( )移除是没有返回值,并且是封装的。
 public E remove(int index) {
	RangeCheck(index);

	modCount++;
	E oldValue = (E) elementData[index];//保存一下index处要移除的元素

	int numMoved = size - index - 1;//和fastRemove方法一样 计算index后面的元素个数
	if (numMoved > 0)
	    System.arraycopy(elementData, index+1, elementData, index,
			     numMoved);
	elementData[--size] = null; // Let gc do its work

	return oldValue;
    }

移除指定一段的元素,实际移除元素之后是一个前闭后开的区间[fromIndex, toIndex)

   protected void removeRange(int fromIndex, int toIndex) {
	modCount++;
	int numMoved = size - toIndex;//移除的区间[fromIndex, toIndex]是在0-size里面的
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);//还是通过拷贝数组完成的移除

	// Let gc do its work
	int newSize = size - (toIndex-fromIndex);//计算新的数组元素个数
	while (size != newSize)
	    elementData[--size] = null; //把toIndex后面到size那里元素赋值为null 交给gc释放
    }

最终的结果就是{8,9,30,16,12}。

7、clear()方法

清空arrayList的存储部分,size为0 其实也可以直接size为0,下次添加元素的时候覆盖掉,但是这并没有在内存的角度上清空

public void clear() {
	modCount++;

	// Let gc do its work
	for (int i = 0; i < size; i++)
	    elementData[i] = null;//把数组里面的每个元素赋值为null 每份空间都是空闲的了

	size = 0;//size重置为0 这又是一个新的arrayList了
    }
        总的来说,arrayList的本质就是通过扩容和拷贝数组完成的这些功能,比起链表要轻便的多,但是也付出了空间的代价,多申请一部分空间,各有优缺点,对于双向链表,插入和删除操作是数组所不能比的。

猜你喜欢

转载自blog.csdn.net/yvken_zh/article/details/80535169
今日推荐