死磕Java容器—ArrayList

深入了解ArrayList。

环境:

  • eclipse 2019-03 (4.11.0)
  • jdk-12.0.1

eclipse中查看源码的方法:按住Ctrl键,鼠标左键单击代码(更详细请百度)。

容器:在Java中,“集合”有另外的用途,所以ArrayList、HashMap等皆称为容器类,其创建的一个对象就是一个容器。

博文中涉及到的源码:

  1. 类声明
  2. 字段
  3. 构造器
  4. size(),isEmpty(),contains(),indexof(),lastIndexOf(),toArray()
  5. 获得元素get()
  6. 修改元素set()
  7. 添加元素add()
  8. 删除元素remove()
  9. 去除所有空trimToSize()
  10. 扩容机制
  11. 清空所有元素clear()
  12. 追加一个数组addAll()
  13. 移除元素集合removeAll()
  14. 遍历元素iterator()
  15. 自定义序列化readObject(),writeObject()
  16. 重写equals()和hashCode()

简介

ArrayList是一个基于数组结构的List,与数组的最大区别在于其可以实现动态扩容。

ArrayList源码分析

1、类声明

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

源码分析:

ArrayList继承了抽象类AbstractList的同时还实现了接口List。AbstractList类声明:

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>{...}

可以看到AbstractList也实现了List接口,和HashMap等一样,这样的做法原因公认度最高的有两种:1)、添加List接口是为了Class类的getInterfaces这个方法可以直接返回List接口。2)、这就是写法上的错误,并没有什么深意,这是得票最高的答案,回答者称曾问过此段代码的设计者Josh Bloch。

ArrayList实现了接口RandomAccess。RandomAccess声明:

public interface RandomAccess {}

查看源码可以看到RandomAccess只是一个标识接口,实现该接口的作用是能支持快速随机访问,具体可以看看ArrayList集合实现RandomAccess接口有何作用?为何LinkedList集合却没实现这接口?

实现Cloneable接口(标识接口),该接口不包含任何方法,实现它仅仅是为了指示可以使用Object类中的clone()方法来进行克隆。

public interface Cloneable {}

实现Serializable接口(标识接口),表示该类可以进行序列化。该接口表示所有的非transient和非static字段都可以被序列化。如果要实现transient字段与static字段的序列化保存,必须声明writeObject和readObject方法

public interface Serializable {}

2、字段

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;

源码分析:

DEFAULT_CAPACITY:数组的默认大小。

EMPTY_ELEMENTDATA :空数组。

DEFAULTCAPACITY_EMPTY_ELEMENTDATA:空数组。在调用无参构造器创建ArrayList对象时使用。

elementData:数组,用于存储元素。

size:int类型,用于记录已存储的元素数量。

除了以上的字段之外,ArrayList中还有两个字段是必须要知道的:

protected transient int modCount = 0;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//2147483631

第一个modCount:该字段不是在ArrayList中声明的,而是其父类AbstractList中的字段,用于记录ArrayList结构性变化的次数(涉及到结构变化的操作如add(),remove()等)。由于此字段的存在,在遍历删除元素的时候有很多注意事项,更多可以看看ArrayList.remove()的正确用法(Java随笔)

第二个MAX_ARRAY_SIZE :该字段、、、算是一个标准吧。一般情况下,数组的最大容量就是该值,但是如果要分配的数组容量大于了该值,就会使用Integer.MAX_VALUE作为数组容量。两者相差8。

3、构造器

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) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

源码分析:ArrayList有三个构造器

ArrayList(int initialCapacity):该构造函数要求传入一个int值,作为数组的初始大小。

ArrayList():无参构造器,创建一个空数组。

ArrayList(Collection<? extends E> c):根据已有的Collection容器对象创建新的ArrayList对象。

4、size(),isEmpty(),contains(),indexof(),lastIndexOf(),toArray()

public int size() {
        return size;//返回已有元素数量
    }

public boolean isEmpty() {
        return size == 0;//返回true或false,判断容器是否没有存储数据
    }

public boolean contains(Object o) {
        return indexOf(o) >= 0;//返回true或false,判断容器是否包含指定元素
    }

public int indexOf(Object o) {
        return indexOfRange(o, 0, size);//根据元素值获得元素在容器数组中的位置索引,从前往后
    }
/*根据元素值获得元素在容器数组中的位置索引分两步:
*1、指定元素为null,应该使用“==”进行判断
*2、指定元素不为null,应使用“equals()”进行判断
*/
int indexOfRange(Object o, int start, int end) {
        Object[] es = elementData;
        if (o == null) {
            for (int i = start; i < end; i++) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = start; i < end; i++) {
                if (o.equals(es[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

public int lastIndexOf(Object o) {//根据元素值获得元素在容器数组中的位置索引,从后往前
        return lastIndexOfRange(o, 0, size);
    }
int lastIndexOfRange(Object o, int start, int end) {
        Object[] es = elementData;
        if (o == null) {
            for (int i = end - 1; i >= start; i--) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = end - 1; i >= start; i--) {
                if (o.equals(es[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

public Object clone() {//重写Object类中的clone方法,浅克隆
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

public Object[] toArray() {//返回一个新数组,数组中包含容器中所用元素。
        return Arrays.copyOf(elementData, size);
    }

@SuppressWarnings("unchecked")
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)//若指定数组太大,将size位置设置为null
            a[size] = null;
        return a;
    }

补充:ArrayList中很多地方都用到了Arrays.copyOf()或System.arraycopy()进行数组的复制,两者具体的区别在

  • Arrays.copyOf()返回一个新数组
  • System.arraycopy()没有返回值,是将数据复制到给定的数组中

Arrays.copyOf()和System.arraycopy()的源码分析可以看看Arrays.copyOf()&System.arraycopy()

5、获得元素get()

public E get(int index) {
        Objects.checkIndex(index, size);//Object中的静态方法,用于判断index范围
        return elementData(index);
    }

源码分析:get(int index)根据位置索引获得元素。

判断index时,若(index<0||index>=size)则会抛出异常outOfBoundsCheckIndex。这里限制index的上限为size是非常有必要的,因为所有元素的索引值都在size以内,而size是小于数组长度的,若没有限制index的上限为size,则获取的值将是null值而不会报出任何异常。

elementData()返回数组elementData中索引index处的元素。该方法是用于获取元素,在ArrayList源码中所有获取数组中的元素都是调用该方法,而不是直接操作数组。

@SuppressWarnings("unchecked")
E elementData(int index) {
        return (E) elementData[index];
    }

这里有一个设计问题,为什么不将判断索引大小的语句Objects.checkIndex(index, size);放于elementData()方法中,而是写进了get()方法?(接下来的set(),remove()等方法中皆是如此)为何要将该语句在每一个方法中都写一遍,而不是直接写在elementData()中。写个模仿ArrayList获取元素的程序来分析:

import java.util.Objects;
public class Run {
	int[] datas={10,20};
	int elementData(int index) {
		return datas[index];
	}
	public int get(int index) {
		Objects.checkIndex(index, 2);
		return elementData(index);
	}
	//main
	public static void main(String[] args) {
		Run run=new Run();
		System.out.println(run.get(-1));
	}
}

这是ArrayList中的写法,将索引设置为-1,运行该程序,报错信息为

将Objects.checkIndex(index, 2);放入elementData()中后再运行程序,报错信息为

对比一下两个错误信息,不同之处在于修改后的程序报错信息多了一句at com.reflect.test.Run.elementData(Run.java:11),指向错误来源为该方法中,但是elementData()方法的权限声明并不是public的,意味着我们并不希望客户端知道elementData()这个方法的存在,而报错信息却表明了该方法,违背了封装原则。所以应该将判断index大小的语句放置在我们希望能被客户端知道的方法中。

6、修改元素set()

public E set(int index, E element) {
        Objects.checkIndex(index, size);//检查index大小是否符合要求
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

源码分析:set()方法修改数据,返回原数据

第一步依然是判断指定位置索引index大小是否在0~size-1中,若(index<0||index>=size)则会抛出异常outOfBoundsCheckIndex。

接着获取原数据,设置新数据,返回原数据,程序结束。

7、添加元素add()

private void add(E e, Object[] elementData, int s) {//重载方法,方法私有,供内部方法调用
        if (s == elementData.length)
            elementData = grow();//若元素个数s已经达到了数组的大小,调用grow()扩容
        elementData[s] = e;
        size = s + 1;
    }
public boolean add(E e) {//追加元素
        modCount++;//记录结构改变次数
        add(e, elementData, size);//调用重载方法
        return true;
    }
public void add(int index, E element) {//在指定位置添加元素
        rangeCheckForAdd(index);//若(index > size || index < 0)则抛出IndexOutOfBoundsException(outOfBoundsMsg(index))异常。
        modCount++;//记录结构改变次数
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();//若size已经达到了数组的大小,调用grow()扩容
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);//从插入位置index开始,后面的元素异常往后移
        elementData[index] = element;
        size = s + 1;
    }

源码分析:ArrayList向客户端代码提供了两个可访问的添加元素方法。

add(E e):在数组最后一个元素后面添加元素。调用了重载方法add(E e, Object[] elementData, int s)。返回true。

add(int index, E element):在指定位置添加元素。使用了System.arraycopy()来实现数据的移动。

8、删除元素remove()

public E remove(int index) {//根据索引删除元素,删除成功返回原数据
        Objects.checkIndex(index, size);//检查index是否符合要求
        final Object[] es = elementData;

        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);//调用方法,实现快速移除

        return oldValue;//返回原数据
    }
public boolean remove(Object o) {//删除指定元素,删除成功返回true,失败返回false
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;//跳出found域
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;//跳出found域
            }
            return false;
        }
        fastRemove(es, i);//调用方法,实现快速移除
        return true;
    }
private void fastRemove(Object[] es, int i) {//快速删除元素
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;//非常有必要的一步。置空最后一位,方便GC快速回收。
    }

源码分析:ArrayList提供了两个可访问的remove()方法

remove(int index):在指定位置移除元素。移除成功,返回被移除的元素值

remove(Object o):移除指定元素。移除成功返回true,失败返回false

快速删除元素是利用System.arraycopy()来实现的,本地方法比循环移动元素更快。

9、去除所有空trimToSize()

public void trimToSize() {//将数组大小重新定义为size,使得数组中没有null空值
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

补充:trimToSize()使用了Arrays.copyOf()来实现重新设置数组大小,得到的是一个新的数组对象。

10、扩容机制

public void ensureCapacity(int minCapacity) {//确定数组容量,
        if (minCapacity > elementData.length
            && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                 && minCapacity <= DEFAULT_CAPACITY)) {
            modCount++;//结构将改变,记录数加一
            grow(minCapacity);//调用grow()方法
        }
    }

private Object[] grow(int minCapacity) {//返回扩容后的新数组,主要用于addAll()
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

private Object[] grow() {//主要用于add()
        return grow(size + 1);
    }

private int newCapacity(int minCapacity) {//计算获得新数组的容量
        // overflow-conscious code
        int oldCapacity = elementData.length;//原数组容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);//计算后的新容量,原来的3/2
        if (newCapacity - minCapacity <= 0) {//若自定义的新容量更大
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;//问题,问题,问题????
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)//判断新容量是否超过了MAX_ARRAY_SIZE
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

private static int hugeCapacity(int minCapacity) {//最大容量判断
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE)
            ? Integer.MAX_VALUE
            : MAX_ARRAY_SIZE;//两者相差8
    }

源码分析:ArrayList扩容机制实现步骤为

  1. 计算扩容后的大小(多数时候是扩容为原容量的3/2倍)。
  2. 判断扩容后的大小是否满足要求,做出相应处理。
  3. 使用Arrays.copyOf()进行扩容,返回新数组。

问题,在方法 newCapacity(int minCapacity)中

此处直接返回了minCapacity,而没有与MAX_ARRAY_SIZE进行比较。由于minCapacity可能存在很大的情况,于是会出现的问题是最终的数组容量远大于MAX_ARRAY_SIZE。在以前的版本中,ArrayList关于扩容的源码在涉及到这一步时是这样写得

至于为什么现在的版本中不这样写了,俺也不知道,俺也莫法问。

11、清空所有元素clear()

public void clear() {//清空所有元素
        modCount++;//记录结构改变
        final Object[] es = elementData;
        for (int to = size, i = size = 0; i < to; i++)
            es[i] = null;//循环清空所有元素
    }

12、追加一个数组addAll()

public boolean addAll(Collection<? extends E> c) {//追加另一个数组
        Object[] a = c.toArray();
        modCount++;//记录结构改变
        int numNew = a.length;
        if (numNew == 0)
            return false;
        Object[] elementData;
        final int s;
        if (numNew > (elementData = this.elementData).length - (s = size))
            elementData = grow(s + numNew);//扩容
        System.arraycopy(a, 0, elementData, s, numNew);
        size = s + numNew;
        return true;
    }
public boolean addAll(int index, Collection<? extends E> c) {//在指定位置开始插入一个数组
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        modCount++;
        int numNew = a.length;
        if (numNew == 0)
            return false;
        Object[] elementData;
        final int s;
        if (numNew > (elementData = this.elementData).length - (s = size))
            elementData = grow(s + numNew);//扩容

        int numMoved = s - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index,
                             elementData, index + numNew,
                             numMoved);
        System.arraycopy(a, 0, elementData, index, numNew);
        size = s + numNew;
        return true;
    }

13、移除元素集合removeAll()

public boolean removeAll(Collection<?> c) {//移除集合c与当前集合的所有交集
        return batchRemove(c, false, 0, size);
    }
boolean batchRemove(Collection<?> c, boolean complement,
                        final int from, final int end) {
        Objects.requireNonNull(c);
        final Object[] es = elementData;
        int r;
        // Optimize for initial run of survivors
        for (r = from;; r++) {
            if (r == end)
                return false;
            if (c.contains(es[r]) != complement)
                break;
        }
        int w = r++;
        try {
            for (Object e; r < end; r++)
                if (c.contains(e = es[r]) == complement)
                    es[w++] = e;
        } catch (Throwable ex) {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            System.arraycopy(es, r, es, w, end - r);
            w += end - r;
            throw ex;
        } finally {
            modCount += end - w;
            shiftTailOverGap(es, w, end);//整理数据,clear to let GC do its work
        }
        return true;
    }

源码分析:removeAll()和一样,都是通过batchRemove()来实现对Collection集合与当前容器操作的。

在batchRemove()中通过contains()找出Collection集合与当前集合的交集。contains()中用到了equest()来判断数据是否相同。

在batchRemove()中通过shiftTailOverGap()来清除当前容器中与Collection集合的所有交集。

private void shiftTailOverGap(Object[] es, int lo, int hi) {//clear to let GC do its work
        System.arraycopy(es, hi, es, lo, size - hi);
        for (int to = size, i = (size -= hi - lo); i < to; i++)
            es[i] = null;
    }

14、遍历元素iterator()

public Iterator<E> iterator() {//返回迭代器对象,封装在容器内部的迭代器
        return new Itr();
    }

private class Itr implements Iterator<E> {//迭代器实现类,主要了解hasNext(),next()
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        // prevent creating a synthetic constructor
        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;//ArrayList.this是ArrayList对象自身引用。这种用法在Java编程思想第193页有提到。
            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
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i < size) {
                final Object[] es = elementData;
                if (i >= es.length)
                    throw new ConcurrentModificationException();
                for (; i < size && modCount == expectedModCount; i++)
                    action.accept(elementAt(es, i));
                // update once at end to reduce heap write traffic
                cursor = i;
                lastRet = i - 1;
                checkForComodification();
            }
        }

        final void checkForComodification() {//检查结构的改变次数
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

源码分析:关于迭代的使用,最常用的方法是hasNext()和next()

hasNext():判断是否还有下一个元素

next():获取下一个元素

每次执行next()时,都会有一个关于modCount的检查,若检查的结果是false,则表示列表数据进行了结构性的变化,会抛出ConcurrentModificationException异常。

final void checkForComodification() {//检查结构的改变次数
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

15、自定义序列化readObject(),writeObject()

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 behavioral compatibility with clone()
        s.writeInt(size);//write与read一一对应

        // 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();
        }
    }

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // like clone(), allocate array based upon size not capacity
            SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
            Object[] elements = new Object[size];

            // Read in all elements in the proper order.
            for (int i = 0; i < size; i++) {
                elements[i] = s.readObject();
            }

            elementData = elements;
        } else if (size == 0) {
            elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new java.io.InvalidObjectException("Invalid size: " + size);
        }
    }

源码分析:writeObject(java.io.ObjectOutputStream s)方法和readObject(java.io.ObjectInputStream s)方法是实现序列化的两个重要方法,声明这两个方法的目的是为了实现自己可控制的序列化

序列化时,ObjectOutputStream会采用反射查找Serializable实现类内部是否声明了这两个方法,若有,则调用该方法。否则将采用默认的序列化进程。

16、重写equals()和hashCode()

//equals()
public boolean equals(Object o) {
        if (o == this) {
            return true;
        }

        if (!(o instanceof List)) {
            return false;
        }

        final int expectedModCount = modCount;
        // ArrayList can be subclassed and given arbitrary behavior, but we can
        // still deal with the common case where o is ArrayList precisely
        boolean equal = (o.getClass() == ArrayList.class)
            ? equalsArrayList((ArrayList<?>) o)
            : equalsRange((List<?>) o, 0, size);

        checkForComodification(expectedModCount);
        return equal;
    }

boolean equalsRange(List<?> other, int from, int to) {
        final Object[] es = elementData;
        if (to > es.length) {
            throw new ConcurrentModificationException();
        }
        var oit = other.iterator();
        for (; from < to; from++) {
            if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) {
                return false;
            }
        }
        return !oit.hasNext();
    }

private boolean equalsArrayList(ArrayList<?> other) {
        final int otherModCount = other.modCount;
        final int s = size;
        boolean equal;
        if (equal = (s == other.size)) {
            final Object[] otherEs = other.elementData;
            final Object[] es = elementData;
            if (s > es.length || s > otherEs.length) {
                throw new ConcurrentModificationException();
            }
            for (int i = 0; i < s; i++) {
                if (!Objects.equals(es[i], otherEs[i])) {
                    equal = false;
                    break;
                }
            }
        }
        other.checkForComodification(otherModCount);
        return equal;
    }

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

//hashCode()
public int hashCode() {
        int expectedModCount = modCount;
        int hash = hashCodeRange(0, size);
        checkForComodification(expectedModCount);
        return hash;
    }

int hashCodeRange(int from, int to) {
        final Object[] es = elementData;
        if (to > es.length) {
            throw new ConcurrentModificationException();
        }
        int hashCode = 1;
        for (int i = from; i < to; i++) {
            Object e = es[i];
            hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
        }
        return hashCode;
    }

源码分析:

hashCode():基本上所有的hashCode都是一样的算法,ArrayList中使用了容器中所有元素的hashCode()值来计算ArrayList对象的hashCode值。

值得注意的是,这两个方法中都会检测modCount值是否被改变。如果该值被改变,则表示容器发生结构性改变,会抛出ConcurrentModificationException异常。

总结

ArrayList的实质就是一个数组,只不过数组的大小不能随意更改,而ArrayList可以。

  1. ArrayList扩容的代价是很大的,扩容机制的核心是创建一个大小适合的新数组对象,然后将原数组中的所有数据复制到新数组中,涉及到大量数据的转移。
  2. 由于扩容代价大,因此在创建ArrayList对象的时候,如果能明确自己的需求,应该给定一个容器大小的初始值,避免扩容机制的不断触发。

虽然ArrayList的实质就是一个数组,但并不意味着我们应该在数组和ArrayList之间优先使用ArrayList。

  1. 当存储的元素是基本类型时,应该综合Java的运行时存储机制进行考虑
  2. 列表存储基本类型数据时,所有的数据都是存储在栈中,访问速度快且占用内存小。
  3. 包括HashMap,ArrayList在内的所有的容器,都不能直接存储基本类型数据,所有的基本数据类型都会被自动包装成对应的包装器类。
  4. 在用ArrayList存储基本类型数据时,由于自动包装机制,每一个基本类型数据都会被包装为一个对象,即所有的数据都会以对象的形式存储在堆中。(当然不是所有的对象都存储在堆中Java对象的创建及存储位置

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章