• ArrayList源码解析


    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;
        }
        ...
    }
    
  • 相关阅读:
    浏览器基础知识点及常考面试题
    java设计模式之综述
    maven的基本原理和使用
    maven的介绍和安装
    Spring整合Struts2的方法
    Spring整合Hibernate的方法
    Spring中的事务管理
    Spring中的JDBC操作
    基于XML配置的Sping AOP详解
    基于注解的Sping AOP详解
  • 原文地址:https://www.cnblogs.com/jordan95225/p/13580453.html
Copyright © 2020-2023  润新知