java8 ArrayList源码阅读【2】- 总结
2017-04-16 13:33
513 查看
上一篇文章 java8 ArrayList源码阅读已经分析了ArrayList源码,现对ArrayList做个小结。
ArrayList一个动态数组,其本质也是用数组实现的,它具有:随机访问速度快,插入和移除性能较差(数组的特点);支持null元素;有顺序;元素可以重复;线程不安全;
ArrayList实现了List接口以及list相关的所有方法,它允许所有元素的插入,包括null。另外,ArrayList和Vector除了线程不同步之外,大致相等。
ArrayList继承AbstractList并实现了List接口,RandomAccess接口,Cloneable接口,Serializable接口,因此它支持快速访问,可复制,可序列化。
ArrayList 默认初始化大小为0(不是10)
ArrayList内部数组的扩容:
当向ArrayList添加新元素前,会调用ensureCapacityInternal(int)方法来保证内部数组空间足够。
从grow()方法可以看出,如果内部数组空间不够,ArrayList每次扩容都是扩1.5倍,然后调用Arrays类的copyOf方法,把元素重新拷贝到一个新的数组中去。扩1.5倍的原因也好理解,因为扩容本质最后是将元素全部重新拷贝到一个新的数组,是耗时的,所以如果每次add都需要扩容一次,那效率是非常低的。所以预存一定空间以供add使用。
ArrayList内部数组的收缩:
利用trimToSize()方法,由于数组扩容时是扩大1.5倍,因此通常会出现容量大于实际有效元素数量,可以将数组占用内存大小压缩到数组中元素个数,可以在内存紧张时调用。同样也是通过Arrays.copyof方法将元素拷贝到一片新的内寸空间,老的空间由gc回收。
indexOf(Object o)
该方法会根据是否为null使用不同方式判断。如果是元素为null,则直接比较地址,否则使用equals的方法比较,加快比较效率。lastIndexOf(Object o) 同理。
clone() 浅拷贝
举个例子说明:
从上述输出结果可以看出,尽管克隆的数组不是跟原数组同一块内存,但内容引用是同样的,指向同一个地址,可以从输出结果看出,因此改变克隆后数组的元素内容,相应的原数组内容发生相应变化,因此clone()方法是浅拷贝。
(注:对于基本类型,如int,则不会发生这种情况。)
add(),addAll(),remove(),removeRange()
关于对数组元素的添加或删除,其实现均是用System.arraycopy()来移动的。
void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
removeAll 和 retainAll
removeAll 是删除指定集合的元素,retainAll则是仅保留指定集合的元素。
而它们背后的实现则比较巧妙。
可以看出它们的实现类似于jvm中垃圾回收机制中的整理-清除方法,即先将需要保留的元素紧凑地移到一块儿,剩下的即为可以删除的元素,然后剩下的元素置null。
modCount变量[fail-fast机制]
记录了数组的修改次数,在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。
该变量迭代器等方面体现。
java.util.ArrayList.Itr()是ArrayList的一个内部类,其包含了一个int型的属性:expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。该内部类的每个方法都涉及到checkForComodification()方法。
因此在使用迭代器迭代过程中,不允许对数组结构做修改,否则会抛出异常 java.util.ConcurrentModificationException。
举例如下:
forEach() java8新增方法
java8前,遍历集合通常使用for循环来遍历,比如遍历输出:
现在可以采用
spliterator() java8新增方法
类似Iiterator,用于多线程,仍需学习。
removeIf() java8新增方法
其实现有点类似batchRemove()的实现方式,先将需要保留的元素紧凑地移到一块儿,剩下的即为可以删除的元素。同时还利用了BitSet来记录要删除元素的下标。
replaceAll() java8新增方法
sort() java8新增方法
java8之前,集合排序要用Collections.sort()方法,现在可以直接用自带的sort方法了。
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; } });
相关文章推荐
- Java源码阅读-ArrayList
- Java源码阅读之ArrayList
- Java深入源码之ArrayList总结
- Java源码阅读——ArrayList
- Java源码阅读之ArrayList
- Java源码阅读之ArrayList(4)
- java8 ArrayList源码阅读
- Java源码阅读之ArrayList
- JAVA 集合类(java.util)源码阅读笔记------ArrayList
- Java源码阅读之ArrayList(1)
- Java源码阅读之ArrayList(3)
- [Java集合源码阅读]-ArrayList扩容机制
- 重拾Java之ArrayList源码阅读
- java源码阅读系列-ArrayList
- Java源码阅读-ArrayList
- java8 ArrayList源码阅读
- Java容器深入研究(jdk 1.8)--- ArrayList总结与源码分析
- java1.7集合源码阅读:ArrayList
- Java源码阅读笔记-ArrayList
- Java源码阅读之ArrayList(终)