Java集合-ArrayList源码

2023-10-17 08:37:21 浏览数 (1)

参数

代码语言:javascript复制
	//默认初始容量
	private static final int DEFAULT_CAPACITY = 10;
	//空数组(用于空实例)
    private static final Object[] EMPTY_ELEMENTDATA = {};
	//用于默认大小空实例的共享空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	//保存数据的数组
    transient Object[] elementData; // non-private to simplify nested class access
	//元素个数
    private int size;

注意:这里有两个空数组,第一个空数组是容量为0的时候的数组,第二个空数组是使用空参构造器的时候的数组

构造方法

代码语言:javascript复制
	//带有参数的构造器

	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);
        }
    }
	
	//这就是与上面不一样的地方
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

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

扩容方法

如果有必要增加此ArrayList实例的容量以确保它至少能容纳元素的数量

代码语言:javascript复制
	public void ensureCapacity(int minCapacity) {
		
		//这里就开始判处出前面两个空数组的区别了
		//如果这个list是无参构造的化没那么minExpand就为10,否则为0
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            ? 
            : DEFAULT_CAPACITY;
		//如果最小容量大于已有的最大容量
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

如果最小所需容量大于了实际可存储容量那么就需要扩容

判断是否真的需要扩容

代码语言:javascript复制
   private void ensureExplicitCapacity(int minCapacity) {
        modCount  ;

        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

这里就是扩容的具体办法其中分为几个情况

最开始新容量等于旧容量的1.5倍

  1. 新容量大等于最小所需容量,那么最后的新容量就等于旧容量的1.5倍
  2. 新容量小于最小所需容量,那么最后的新容量就等于最小所需容量
  3. 新容量大于List中规定最大容量,判断最小所需容量是否大于了List中规定的最小容量,如果没有则新容量还是等于最小容量,如果大于了则新容量等于Integer的最大值
代码语言:javascript复制
    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);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
代码语言:javascript复制
 	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

计算最小容量是否会等于10

代码语言:javascript复制
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

增删改查方法

增删改查的方法都是十分简单的所以不过多赘述

但是要注意在增加和删除方法的每一次调用的时候都会使modCount

Iterator迭代器

在ArrayList内部有个Itr的内部类

代码语言:javascript复制
private class Itr implements Iterator<E> {
		//当前游标位置
        int cursor; 
        //上一个游标位置
        int lastRet = -1; 
        //修改次数
        int expectedModCount = modCount;

        Itr() {}
		
		//是否还存在下一个
        public boolean hasNext() {
            return cursor != size;
        }
        
        @SuppressWarnings("unchecked")
        public E next() {
        
        	//检查期待修改次数与实际的修改次数是否发生变化
        	//如果有变化就抛出异常
            checkForComodification();
            
            int i = cursor;
            
            if (i >= size) //判断是否越界
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            
            if (i >= elementData.length) //判断是否越界
                throw new ConcurrentModificationException();
            
            cursor = i   1; //游标变为下一个
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                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();
        }

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

快速失败

前面都说了modCount属性,然后迭代器哪里也使用到了所以这里就说一下它的作用,它的作用就是实现快速失败,每次迭代器遍历的时候都会去查询实际的modCount属性与迭代器中保存的modCount属性是否相同,如果不同那么就抛出异常,这就是快速失败

拓展:安全失败:安全失败使用的写时复制技术,这个迭代器中遍历的数据是复制的数据,所以对于原有数据的修改不会影响到复制的数据

0 人点赞