码迷,mamicode.com
首页 > 其他好文 > 详细

ArrayList源码解析

时间:2020-09-09 18:51:49      阅读:38      评论:0      收藏:0      [点我收藏+]

标签:swa   方式   math   test   class   rom   效率   shift   str   

1.基本原理

底层是一个Object[]数组来维护数据

2.优缺点

1.优点

  • 随机访问性能好(底层是数组)

2.缺点

  • 扩容会影响性能
  • 删除和插入元素时,需要进行拷贝影响性能
  • 线程不安全

3.源码分析

1.add()方法

/**
     * Appends the specified element to the end of this list.
     * 追加一个具体的元素到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) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

ensureCapacityInternal的代码如下:

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

calculateCapacity的代码如下:

这里第一次调用add方法的时候,elementData为空数组,进入if条件,所以calculateCapacity方法返回DEFAULT_CAPACITY变量的值为10,返回给ensureExplicitCapacity方法

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

ensureExplicitCapacity方法如下:

该方法是确保精确的容量,传入的参数为10,如果大于数组长度,即容量不够,会调用grow方法扩容

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

grow方法如下:

这里oldCapacity为0,newCapacity也是0,进入第一个if,则newCapacity为10,然后调用Arrays.copyOf方法拷贝数组

第二个if判断newCapacity如果超过Integer.MAX_VALUE - 8,调用hugeCapacity方法判断,如果大于MAX_ARRAY_SIZE,取Integer.MAX_VALUE,否则取MAX_ARRAY_SIZE

/**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

Arrays.copyOf方法如下:

这里传入的original为Object[],newLength为10,在重载方法中,newType为Object[].class,所以copy为(T[]) new Object[newLength],然后执行System.arraycopy方法进行数组拷贝,即从original数组0位置开始移动0个元素到copy数组,相当于什么都没拷贝,直接返回了新创建的数组T[] copy,所以最终elementData返回了长度为10的数组,意味着确保容量操作已经完成,然后通过elementData[size++] = e完成元素的添加

public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
}

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        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;
}

所以可以看出,当添加元素的时候,如果数组大小不够,会进入grow方法,该方法会对数组进行1.5倍扩容,如果不指定ArrayList大小,就会频繁扩容和拷贝,影响性能

2.add(int index, E element)方法

该方法是在指定位置插入一个元素,代码如下:

public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
}

首先通过rangeCheckForAdd方法检查index是否超标:

private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

然后通过ensureCapacityInternal确保容量,之后调用System.arraycopy方法将index之后长度为size - index的元素移动到index+1位置,最后将element元素插入index位置,将size+1

3.set方法

public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
}

首先是rangeCheck方法,检查index是否超出范围:

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

然后是elementData(int index)方法,通过数组下标获取元素

E elementData(int index) {
        return (E) elementData[index];
    }

最后将index位置的元素用element替换,返回原来的元素

4.get方法

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

首先检查index范围,然后直接通过数组下标返回元素

5.remove方法

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);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

首先校验index是否合法,然后获取替换前的元素,然后计算需要拷贝的元素个数,如果大于0,进行拷贝,最后将数组最后一个元素赋值为null,让GC回收

6.removeIf方法

首先判断filter不为null,然后通过BitSet和for循环找到符合条件的元素,将index放入BitSet中,增加removeCount,如果removeCount大于0,通过for循环进行元素交换,然后把newSize之后的元素置为null,最后返回是否成功

public boolean removeIf(Predicate<? super E> filter) {
  			//判断filter不为null
        Objects.requireNonNull(filter);
        // figure out which elements are to be removed
        // any exception thrown from the filter predicate at this stage
        // will leave the collection unmodified
        int removeCount = 0;
        final BitSet removeSet = new BitSet(size);
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i = 0; modCount == expectedModCount && i < size; i++) {
            @SuppressWarnings("unchecked") final E element = (E) elementData[i];
            if (filter.test(element)) {
                removeSet.set(i);
                removeCount++;
            }
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

        // shift surviving elements left over the spaces left by removed elements
        final boolean anyToRemove = removeCount > 0;
        if (anyToRemove) {
            final int newSize = size - removeCount;
            for (int i = 0, j = 0; (i < size) && (j < newSize); i++, j++) {
                i = removeSet.nextClearBit(i);
                elementData[j] = elementData[i];
            }
            for (int k = newSize; k < size; k++) {
                elementData[k] = null;  // Let gc do its work
            }
            this.size = newSize;
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }

        return anyToRemove;
    }

fail-fast机制

在该过程中使用了modCount和expectedModCount做判断,这两个值表示的是如果执行该方法,开始删除符合条件的元素时,不能有另外的线程修改当前的ArrayList,如果这时候有别的线程修改,那么modCount会发生变化,当发现modCount和执行方法开始时expectedModCount不一致的话,就会抛出ConcurrentModificationException,即并发修改异常,删除失败,这就是fail-fast机制,可以让当前线程快速失败,不会产生资源竞争,同时也导致了ArrayList线程不安全,不能并发操作

7.遍历

方法1通过for循环遍历,通过get方法访问元素

方法2和方法3都是通过迭代器遍历

方法4也是通过for循环和数组的下标来遍历

方法5时通过stream的API操作,效率不如方法4

public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
//        System.out.println(list.set(1,"ddd"));
//        System.out.println(list.get(1));
        //遍历方法1
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        //方法2
        for (String s : list) {
            System.out.println(s);
        }
        //方法3
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            System.out.println(next);
        }
        //方法4
        list.forEach(data -> System.out.println(data));
        //方法5
        list.stream().forEach(data -> System.out.println(data));
    }
}

foreach方法

核心就是for循环,通过数组操作获取对应元素,传递Consumer函数表达式,执行相应逻辑

public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

iterator方法

public Iterator<E> iterator() {
    return new Itr();
}

直接返回一个new Itr()对象,这个内部类是一个迭代器,如下:

这里hasNext方法里面判断cursor的值和数组大小是否相等,如果不相等就返回true,然后next方法里面,checkForComodification方法做并发访问检查,之后用i保存cursor值,然后做校验,之后获取ArrayList的elementData数组,再次做并发访问检查,然后将cursor+1,将i的值赋给lastRet,并且返回数组中的该元素

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    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();
    }
}

ListItr类

该类继承了Itr类,实现了向前遍历的功能

private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;
    }

    public boolean hasPrevious() {
        return cursor != 0;
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor - 1;
    }

    @SuppressWarnings("unchecked")
    public E previous() {
        checkForComodification();
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;
        return (E) elementData[lastRet = i];
    }

    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

小结:

  • ArrayList有两个迭代器Itr和ListItr,前者可以向后迭代,后者既可以向前又可以向后迭代

  • 这两个迭代器不是并发安全的

  • 遍历List的方法主要是通过for循环+数组下标和指针移动两种方式

8.toArray方法

第一个方法底层使用Arrays.copyOf拷贝到数组

第二个方法需要传递一个目标数组,如果目标数组比ArrayList小,通过Arrays.copyOf扩容成和ArrayList大小一样的数组,如果目标数组比ArrayList大,直接使用System.arraycopy拷贝所有元素,并且将后面的元素置为null

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}
public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a‘s runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
}

9.subList方法

首先检查范围,然后创建一个SubList内部对象,然后在构造函数传入了this,即ArrayList创建的对象本身,将其赋给parent,其他方法也是基于原数组的数据进行操作,根据fromIndex和toIndex限定了访问范围

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}
private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;

    SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }

    public E set(int index, E e) {
        rangeCheck(index);
        checkForComodification();
        E oldValue = ArrayList.this.elementData(offset + index);
        ArrayList.this.elementData[offset + index] = e;
        return oldValue;
    }

    public E get(int index) {
        rangeCheck(index);
        checkForComodification();
        return ArrayList.this.elementData(offset + index);
    }

    public int size() {
        checkForComodification();
        return this.size;
    }

    public void add(int index, E e) {
        rangeCheckForAdd(index);
        checkForComodification();
        parent.add(parentOffset + index, e);
        this.modCount = parent.modCount;
        this.size++;
    }

    public E remove(int index) {
        rangeCheck(index);
        checkForComodification();
        E result = parent.remove(parentOffset + index);
        this.modCount = parent.modCount;
        this.size--;
        return result;
    }
    ...
}

ArrayList源码解析

标签:swa   方式   math   test   class   rom   效率   shift   str   

原文地址:https://www.cnblogs.com/jordan95225/p/13580453.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!