ArrayList源码解读(1.8)

        一、数组和ArrayList的区别
            既然提到ArrayList就不能不提一下数组,你知道什么时候用数组,什么时候用ArrayList么?他们有什么区别?
                1.效率:当执行Add、AddRange、Insert、InsertRange等添加元素时,都会检查内部的数组的容量是否达到扩容的界限,如果达到,将会以当前容量大约1.5倍来创建一个新数组,然后将进行数据的Copy,并将引用指向新数组。这个临界点的扩容操作是比较消耗效率的。
                2.扩容:相当与1,数组不能扩容,而ArrayList可以。
                3.存储数据类型:Array 数组可以包含基本类型和对象类型,而ArrayList只能存储对象类型。
                所以,如果数组长度不固定,使用ArrayList。
        二、ArrayList源码:
             ArrayList继承了AbstractList类,实现了List、RandomAccess、Cloneable、Serializable接口。
                 RandomAccess:是一种标记接口,随机访问任意下标元素。当要实现某些算法时,会判断当前类是否实现了RandomAccess接口会根据结果选择不同的算法。
                         

                        public static <T>
                        int binarySearch(List<? extends Comparable<? super T>> list, T key) {
                            if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
                                return Collections.indexedBinarySearch(list, key);
                            else
                                return Collections.iteratorBinarySearch(list, key);
                        }


                    从源码中我们可以看到,在进行二分查找的时候,list会先判断是否是RandomAccess也即是否实现了RandomAccess接口,接着在调用想用的二分查找算法来进行,(其中: BINARYSEARCH_THRESHOLD Collections的一个常量(5000),它是二分查找的阀值。)如果实现了RandomAccess接口的List,执行indexedBinarySearch方法,否则执行 iteratorBinarySearch方法。

                              

                                    private static <T>
                                    int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
                                        int low = 0;
                                        int high = list.size()-1;
                                 
                                        while (low <= high) {
                                            int mid = (low + high) >>> 1;
                                            Comparable<? super T> midVal = list.get(mid);
                                            int cmp = midVal.compareTo(key);
                                 
                                            if (cmp < 0)
                                                low = mid + 1;
                                            else if (cmp > 0)
                                                high = mid - 1;
                                            else
                                                return mid; // key found
                                        }
                                        return -(low + 1);  // key not found
                                       }
                                private static <T>
                                        int iteratorBinarySearch(List<? extends                         Comparable<? super T>> list, T key)
                                        {
                                            int low = 0;
                                            int high = list.size()-1;
                                            ListIterator<? extends Comparable<? super T>> i = list.listIterator();
                                     
                                            while (low <= high) {
                                                int mid = (low + high) >>> 1;
                                                Comparable<? super T> midVal = get(i, mid);
                                                int cmp = midVal.compareTo(key);
                                     
                                                if (cmp < 0)
                                                    low = mid + 1;
                                                else if (cmp > 0)
                                                    high = mid - 1;
                                                else
                                                    return mid; // key found
                                            }
                                            return -(low + 1);  // key not found
                                        }


                    注:实现RandomAccess接口的的List可以通过简单的for循环来访问数据比使用iterator访问来的高效快速。并不是所有List实现RondomAccess接口都可以提高查询效率。JDK中说的很清楚,在对List特别是Huge size的List的遍历算法中,要尽量来判断是属于RandomAccess(如ArrayList)还是Sequence List (如LinkedList),因为适合RandomAccess List的遍历算法,用在Sequence List上就差别很大。
        三、ArrayList成员变量

                public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
                    // 序列化id
                    private static final long serialVersionUID = 8683452581122892189L;
                    // 默认初始的容量
                    private static final int DEFAULT_CAPACITY = 10;
                    // 一个空对象
                    private static final Object[] EMPTY_ELEMENTDATA = {};
                    // 一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值
                     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
                    // 当前数据对象存放地方,当前对象不参与序列化
                    transient Object[] elementData;
                    // 当前数组长度
                    private int size;
                    // 数组最大长度
                    private static final int MAX_ARRAY_SIZE = 2147483639;
                 
                    // 省略方法。。
                }


                1.当创建一个ArrayList对象时,内部的数组长度不为10,而是0.
                2.数组elementData用了transient修饰,不参与序列化。
                    ?:那么如何传输数据,为什么这个数组用transient修饰?
                        因为数组的容量并不是数组数据的长度。而是较大。ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream。

                            private void writeObject(java.io.ObjectOutputStream s)
                                throws java.io.IOException{
                                // Write out element count, and any hidden stuff
                                int expectedModCount = modCount;
                                s.defaultWriteObject();
                             
                                // Write out size as capacity for behavioural compatibility with clone()
                                s.writeInt(size);
                             
                                // Write out all elements in the proper order.
                                for (int i=0; i<size; i++) {
                                    s.writeObject(elementData[i]);
                                }
                             
                                if (modCount != expectedModCount) {
                                    throw new ConcurrentModificationException();
                                }
                            }


                        只将有数据的数组进行序列化,减少冗余数据。
        四、ArrayList

                add(E e)
                    public boolean add(E e) {
                        ensureCapacityInternal(size + 1);  // Increments modCount!!
                        elementData[size++] = e;
                        return true;
                    }


                将数组实际长度+1,并判断是不是新空数组,如果为空,则在10和size+1中取最大值。然后判断长度是否达到限定值。如果达到,则进行扩容。
                contains()

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


                     调用index方法对数组进行遍历,如果找到元素,返回true,否则返回空。
                remove 
                    根据索引remove
                        1.判断索引有没有越界
                        2.自增修改次数
                        3.将指定位置(index)上的元素保存到oldValue
                        4.将指定位置(index)上的元素都往前移动一位
                        5.将最后面的一个元素置空,好让垃圾回收器回收
                        6.将原来的值oldValue返回

                clear方法
                    添加操作次数(modCount),将数组内的元素都置空,等待垃圾收集器收集,不减小数组容量。

                            public void clear() {
                                modCount++;
                         
                                // clear to let GC do its work
                                for (int i = 0; i < size; i++)
                                    elementData[i] = null;
                         
                                size = 0;
                            }


                trimToSize方法
                    1.修改次数加1
                    2.将elementData中空余的空间(包括null值)去除,例如:数组长度为10,其中只有前三个元素有值,其他为空,那么调用该方法之后,数组的长度变为3
                iterator方法
                    interator方法返回的是一个内部类,由于内部类的创建默认含有外部的this指针,所以这个内部类可以调用到外部类的属性。

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


                    一般的话,调用完iterator之后,我们会使用iterator做遍历,这里使用next做遍历的时候有个需要注意的地方,就是调用next的时候,可能会引发ConcurrentModificationException,当修改次数,与期望的修改次数(调用iterator方法时候的修改次数)不一致的时候,会发生该异常,详细我们看一下代码实现:

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


                    expectedModCount这个值是在用户调用ArrayList的iterator方法时候确定的,但是在这之后用户add,或者remove了ArrayList的元素,那么modCount就会改变,那么这个值就会不相等,将会引发ConcurrentModificationException异常,这个是在多线程使用情况下,比较常见的一个异常。

猜你喜欢

转载自blog.csdn.net/qq_40280705/article/details/81943150