java8 ArrayList源码阅读【2】- 总结

上一篇文章 java8 ArrayList源码阅读已经分析了ArrayList源码,现对ArrayList做个小结。

  • ArrayList一个动态数组,其本质也是用数组实现的,它具有:随机访问速度快,插入和移除性能较差(数组的特点);支持null元素;有顺序;元素可以重复;线程不安全;

  • ArrayList实现了List接口以及list相关的所有方法,它允许所有元素的插入,包括null。另外,ArrayList和Vector除了线程不同步之外,大致相等。

  • ArrayList继承AbstractList并实现了List接口,RandomAccess接口,Cloneable接口,Serializable接口,因此它支持快速访问,可复制,可序列化。
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • ArrayList 默认初始化大小为0(不是10)
//在以前的版本,ArrayList 默认初始化大小为10,其默认构造函数为
public ArrayList() {
    this(10);
}

//jdk8版本,默认构造方法,使用空数组初始化,容量大小默认为0
public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA; //EMPTY_ELEMENTDATA={}
}
  • ArrayList内部数组的扩容:
    当向ArrayList添加新元素前,会调用ensureCapacityInternal(int)方法来保证内部数组空间足够。
//添加新元素时使用,使用指定参数设定最小容量,minCapacity为size+1
private void ensureCapacityInternal(int minCapacity) {
   //如果数组为空,使用默认值和参数中较大者作为容量预设值
   if (elementData == EMPTY_ELEMENTDATA) {
       minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
   }
   ensureExplicitCapacity(minCapacity);
}

//根据指定参数增加数组容量
private void ensureExplicitCapacity(int minCapacity) {
    modCount++; //上面提到,只要涉及数组结构的改变(这里是数组大小改变),该变量改变

    //如果参数大于数组容量,就增加数组容量
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

//增加容量,以确保它可以至少持有由参数指定的元素的数目
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;

    //新容量为原来的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);

    //如果仍比参数小,则新容量大小取参数的值
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //若新容量大于默认的最大值,则检查是否溢出
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);

    elementData = Arrays.copyOf(elementData, newCapacity);
}

从grow()方法可以看出,如果内部数组空间不够,ArrayList每次扩容都是扩1.5倍,然后调用Arrays类的copyOf方法,把元素重新拷贝到一个新的数组中去。扩1.5倍的原因也好理解,因为扩容本质最后是将元素全部重新拷贝到一个新的数组,是耗时的,所以如果每次add都需要扩容一次,那效率是非常低的。所以预存一定空间以供add使用。

  • ArrayList内部数组的收缩:
    利用trimToSize()方法,由于数组扩容时是扩大1.5倍,因此通常会出现容量大于实际有效元素数量,可以将数组占用内存大小压缩到数组中元素个数,可以在内存紧张时调用。同样也是通过Arrays.copyof方法将元素拷贝到一片新的内寸空间,老的空间由gc回收。
//释放数组空余的空间,容量为数组实际元素数量(因为容量通常会大于实际元素数量,
public void trimToSize() {
    modCount++;     //涉及数组的改变,modCount都会改变,留意该变量,后面会频繁出现
    if (size < elementData.length) {    //如果数组实际元素数量比数组容量小,则重新拷贝到一片新的内寸空间
        elementData = Arrays.copyOf(elementData, size);
    }
}
  • indexOf(Object o)
    该方法会根据是否为null使用不同方式判断。如果是元素为null,则直接比较地址,否则使用equals的方法比较,加快比较效率。lastIndexOf(Object o) 同理。
//返回特定元素在数组首次出现的位置(遍历的方式),会根据是否为null使用不同方式判断。不存在就返回-1
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i] == null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;  //不存在返回-1
}
  • clone() 浅拷贝
//返回副本,浅拷贝
//复制过程数组发生改变会抛出异常
public Object clone() {
    try {
        //java.lang.Object.clone()只是一种浅复制,所以,v的elementData引用的还是当前ArrayList的elementData的引用
        java.util.ArrayList<?> v = (java.util.ArrayList<?>) super.clone();
        //对原来ArrayList的elementData进行一个数组拷贝,然后赋值给v的elementData
        //这样,v的elementData就是原先ArrayList的elementData的一个副本,不再是内存中同一块数组
        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 class Main {
    static class A {
        int a;

        A(int a) {
            this.a = a;
        }

        @Override
        public String toString() {
            return super.toString() + " : " + a;
        }
    }

    public static void main(String[] args) {
        ArrayList<A> a1=new ArrayList<>();
        a1.add(new A(1));
        a1.add(new A(2));
        ArrayList<A> a2= (ArrayList<A>) a1.clone();
        System.out.println(a1);
        System.out.println("---");
        System.out.println(a1);
        System.out.println("---");
        a2.get(1).a=100;
        System.out.println(a1);
        System.out.println("---");
        System.out.println(a1);
        System.out.println("---");
    }
}
/*
 * 输出结果:
 [Main$A@1b6d3586 : 1, Main$A@4554617c : 2]
 ---
 [Main$A@1b6d3586 : 1, Main$A@4554617c : 2]
 ---
 [Main$A@1b6d3586 : 1, Main$A@4554617c : 100]
 ---
 [Main$A@1b6d3586 : 1, Main$A@4554617c : 100]
 ---
 */

从上述输出结果可以看出,尽管克隆的数组不是跟原数组同一块内存,但内容引用是同样的,指向同一个地址,可以从输出结果看出,因此改变克隆后数组的元素内容,相应的原数组内容发生相应变化,因此clone()方法是浅拷贝。
(注:对于基本类型,如int,则不会发生这种情况。)

  • add(),addAll(),remove(),removeRange()
    关于对数组元素的添加或删除,其实现均是用System.arraycopy()来移动的。
    void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

  • removeAll 和 retainAll
    removeAll 是删除指定集合的元素,retainAll则是仅保留指定集合的元素。

//部分应用场景:
//并集
list1.addAll(list2);
//交集
list1.retainAll(list2);
//差集
list1.removeAll(list2);
//无重复并集
list2.removeAll(list1);
list1.addAll(list2);

而它们背后的实现则比较巧妙。

//删除指定集合的元素
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);  //检查集合是否为空
    return batchRemove(c, false);   //调用batchRemove,complement为false
}

//与removeAll相反,仅保留指定集合的元素
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);  //检查集合是否为空
    return batchRemove(c, true);    //调用batchRemove,complement为true
}

 //complement true时从数组保留指定集合中元素的值,为false时从数组删除指定集合中元素的值。
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;   //w为数组更新后的大小
    boolean modified = false;
    try {
        //遍历数组,并检查元素是否在指定集合中,根据complement的值保留特定值到数组
        //若complement为true即保留,则将相同元素移动到数组前端
        //若complement为false即删除,则将不同元素移动到数组前端
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        //如果r!=size则说明c.contains(elementData[r])抛出异常
        if (r != size) {
            //将数组未遍历的部分添加
            System.arraycopy(elementData, r,
                    elementData, w,
                    size - r);
            w += size - r;
        }
        //如果w!=size说明进行了删除操作,故需将删除的值赋为null
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;   //更新数组容量
            modified = true;
        }
    }
    return modified;
}

可以看出它们的实现类似于jvm中垃圾回收机制中的整理-清除方法,即先将需要保留的元素紧凑地移到一块儿,剩下的即为可以删除的元素,然后剩下的元素置null。

  • modCount变量[fail-fast机制]
    记录了数组的修改次数,在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。
    该变量迭代器等方面体现。
//返回普通迭代器
public Iterator<E> iterator() {
    return new java.util.ArrayList.Itr();
}

java.util.ArrayList.Itr()是ArrayList的一个内部类,其包含了一个int型的属性:expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。该内部类的每个方法都涉及到checkForComodification()方法。

//检查数组是否修改,根据expectedModCount和modCount判断
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

因此在使用迭代器迭代过程中,不允许对数组结构做修改,否则会抛出异常 java.util.ConcurrentModificationException。
举例如下:

public class Main {
    static class A {
        int a;

        A(int a) {
            this.a = a;
        }

        @Override
        public String toString() {
            return super.toString() + " : " + a;
        }
    }

    public static void main(String[] args) {
        ArrayList<A> a1=new ArrayList<>();
        a1.add(new A(1));
        a1.add(new A(2));
        Iterator i=a1.iterator();
        System.out.println(i.next()+"");
        a1.get(1).a=100;    //ok
        a1.add(new A(3));   //error 
        System.out.println(i.next()+"");
    }
}
/*
 * 输出为:
 Main$A@1b6d3586 : 1
 Exception in thread "main" java.util.ConcurrentModificationException 
 */
  • forEach() java8新增方法
 //遍历每个元素做指定操作,java8新增方法
@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);  //检查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();
    }
}

java8前,遍历集合通常使用for循环来遍历,比如遍历输出:

for(A a:aList){
    System.out.println(a);
}

现在可以采用

// lambda
aList.foreach(a -> System.out.println(a);
// or
aList.forEach(new Consumer<A>() {
    @Override
    public void accept(A a) {
        System.out.println(a);
    }
});
  • spliterator() java8新增方法
    类似Iiterator,用于多线程,仍需学习。

  • removeIf() java8新增方法
    其实现有点类似batchRemove()的实现方式,先将需要保留的元素紧凑地移到一块儿,剩下的即为可以删除的元素。同时还利用了BitSet来记录要删除元素的下标。

//判断条件是否满足,满足则删除,java8新增方法
@Override
public boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);     //检查filter是否为空
    int removeCount = 0;    //记录要删除的数目
    final BitSet removeSet = new BitSet(size);  //利用BitSet来记录删除元素的下标
    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];
        //如果当前元素符合条件,则BitSet标记当前下标
        if (filter.test(element)) {
            removeSet.set(i);
            removeCount++;
        }
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }

    final boolean anyToRemove = removeCount > 0;    //根据removeCount变量判断是否有满足条件的元素
    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];
        }
        //将更新后的数组没用的元素置null
        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;
}
  • replaceAll() java8新增方法
//根据operator进行替换,java8新增方法
@Override
@SuppressWarnings("unchecked")
public void replaceAll(UnaryOperator<E> operator) {
    Objects.requireNonNull(operator);
    final int expectedModCount = modCount;
    final int size = this.size;
    for (int i = 0; modCount == expectedModCount && i < size; i++) {
        elementData[i] = operator.apply((E) elementData[i]);
    }
    //检查数组是否修改
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}
  • sort() java8新增方法
 //根据Comparator排序,java8新增方法
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    //检查数组是否修改
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

java8之前,集合排序要用Collections.sort()方法,现在可以直接用自带的sort方法了。

// lambda
aList.sort((a,b)->(a.a-b.b));
// or
a1.sort(new Comparator<A>() {
    @Override
    public int compare(A o1, A o2) {
        return o1.a-o2.a;
    }
});
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值