From c5ef4d69b962668d57cce50e699a66b4e0f110cf Mon Sep 17 00:00:00 2001 From: Hiroshi Ito Date: Mon, 23 May 2016 23:12:05 +0800 Subject: [PATCH] 8158365: List.spliterator should optimize for RandomAccess lists Reviewed-by: psandoz --- .../share/classes/java/util/AbstractList.java | 111 +++++++++++++ .../share/classes/java/util/List.java | 25 ++- .../SpliteratorLateBindingFailFastTest.java | 43 ++++++ ...SpliteratorTraversingAndSplittingTest.java | 146 ++++++++++++++++++ 4 files changed, 321 insertions(+), 4 deletions(-) diff --git a/jdk/src/java.base/share/classes/java/util/AbstractList.java b/jdk/src/java.base/share/classes/java/util/AbstractList.java index 3168a738acc..c8c160de6a4 100644 --- a/jdk/src/java.base/share/classes/java/util/AbstractList.java +++ b/jdk/src/java.base/share/classes/java/util/AbstractList.java @@ -25,6 +25,8 @@ package java.util; +import java.util.function.Consumer; + /** * This class provides a skeletal implementation of the {@link List} * interface to minimize the effort required to implement this interface @@ -634,6 +636,115 @@ public abstract class AbstractList extends AbstractCollection implements L return "Index: "+index+", Size: "+size(); } + /** + * An index-based split-by-two, lazily initialized Spliterator covering + * a List that access elements via {@link List#get}. + * + * If access results in an IndexOutOfBoundsException then a + * ConcurrentModificationException is thrown instead (since the list has + * been structurally modified while traversing). + * + * If the List is an instance of AbstractList then concurrent modification + * checking is performed using the AbstractList's modCount field. + */ + static final class RandomAccessSpliterator implements Spliterator { + + private final List list; + private int index; // current index, modified on advance/split + private int fence; // -1 until used; then one past last index + + // The following fields are valid if covering an AbstractList + private final AbstractList alist; + private int expectedModCount; // initialized when fence set + + RandomAccessSpliterator(List list) { + assert list instanceof RandomAccess; + + this.list = list; + this.index = 0; + this.fence = -1; + + this.alist = list instanceof AbstractList ? (AbstractList) list : null; + this.expectedModCount = alist != null ? alist.modCount : 0; + } + + /** Create new spliterator covering the given range */ + private RandomAccessSpliterator(RandomAccessSpliterator parent, + int origin, int fence) { + this.list = parent.list; + this.index = origin; + this.fence = fence; + + this.alist = parent.alist; + this.expectedModCount = parent.expectedModCount; + } + + private int getFence() { // initialize fence to size on first use + int hi; + List lst = list; + if ((hi = fence) < 0) { + if (alist != null) { + expectedModCount = alist.modCount; + } + hi = fence = lst.size(); + } + return hi; + } + + public Spliterator trySplit() { + int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; + return (lo >= mid) ? null : // divide range in half unless too small + new RandomAccessSpliterator<>(this, lo, index = mid); + } + + public boolean tryAdvance(Consumer action) { + if (action == null) + throw new NullPointerException(); + int hi = getFence(), i = index; + if (i < hi) { + index = i + 1; + action.accept(get(list, i)); + checkAbstractListModCount(alist, expectedModCount); + return true; + } + return false; + } + + public void forEachRemaining(Consumer action) { + Objects.requireNonNull(action); + List lst = list; + int hi = getFence(); + int i = index; + index = hi; + for (; i < hi; i++) { + action.accept(get(lst, i)); + } + checkAbstractListModCount(alist, expectedModCount); + } + + public long estimateSize() { + return (long) (getFence() - index); + } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED; + } + + private static E get(List list, int i) { + try { + return list.get(i); + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + static void checkAbstractListModCount(AbstractList alist, int expectedModCount) { + if (alist != null && alist.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + } + private static class SubList extends AbstractList { private final AbstractList root; private final SubList parent; diff --git a/jdk/src/java.base/share/classes/java/util/List.java b/jdk/src/java.base/share/classes/java/util/List.java index d4af1a31d49..3819d94e831 100644 --- a/jdk/src/java.base/share/classes/java/util/List.java +++ b/jdk/src/java.base/share/classes/java/util/List.java @@ -741,9 +741,22 @@ public interface List extends Collection { * * @implSpec * The default implementation creates a - * late-binding spliterator - * from the list's {@code Iterator}. The spliterator inherits the - * fail-fast properties of the list's iterator. + * late-binding + * spliterator as follows: + *
    + *
  • If the list is an instance of {@link RandomAccess} then the default + * implementation creates a spliterator that traverses elements by + * invoking the method {@link List#get}. If such invocation results or + * would result in an {@code IndexOutOfBoundsException} then the + * spliterator will fail-fast and throw a + * {@code ConcurrentModificationException}. + * If the list is also an instance of {@link AbstractList} then the + * spliterator will use the list's {@link AbstractList#modCount modCount} + * field to provide additional fail-fast behavior. + *
  • Otherwise, the default implementation creates a spliterator from the + * list's {@code Iterator}. The spliterator inherits the + * fail-fast of the list's iterator. + *
* * @implNote * The created {@code Spliterator} additionally reports @@ -754,7 +767,11 @@ public interface List extends Collection { */ @Override default Spliterator spliterator() { - return Spliterators.spliterator(this, Spliterator.ORDERED); + if (this instanceof RandomAccess) { + return new AbstractList.RandomAccessSpliterator<>(this); + } else { + return Spliterators.spliterator(this, Spliterator.ORDERED); + } } /** diff --git a/jdk/test/java/util/Spliterator/SpliteratorLateBindingFailFastTest.java b/jdk/test/java/util/Spliterator/SpliteratorLateBindingFailFastTest.java index dc961b15e5a..0aecca604bd 100644 --- a/jdk/test/java/util/Spliterator/SpliteratorLateBindingFailFastTest.java +++ b/jdk/test/java/util/Spliterator/SpliteratorLateBindingFailFastTest.java @@ -24,11 +24,13 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; +import java.util.Iterator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -37,6 +39,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.PriorityQueue; +import java.util.RandomAccess; import java.util.Set; import java.util.Spliterator; import java.util.Stack; @@ -191,6 +194,46 @@ public class SpliteratorLateBindingFailFastTest { db.addList(Vector::new); + class AbstractRandomAccessListImpl extends AbstractList implements RandomAccess { + List l; + + AbstractRandomAccessListImpl(Collection c) { + this.l = new ArrayList<>(c); + } + + @Override + public boolean add(Integer integer) { + modCount++; + return l.add(integer); + } + + @Override + public Iterator iterator() { + return l.iterator(); + } + + @Override + public Integer get(int index) { + return l.get(index); + } + + @Override + public boolean remove(Object o) { + modCount++; + return l.remove(o); + } + + @Override + public int size() { + return l.size(); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return l.subList(fromIndex, toIndex); + } + } + db.addList(AbstractRandomAccessListImpl::new); db.addCollection(HashSet::new); diff --git a/jdk/test/java/util/Spliterator/SpliteratorTraversingAndSplittingTest.java b/jdk/test/java/util/Spliterator/SpliteratorTraversingAndSplittingTest.java index a823ac04833..a8d6a9c6471 100644 --- a/jdk/test/java/util/Spliterator/SpliteratorTraversingAndSplittingTest.java +++ b/jdk/test/java/util/Spliterator/SpliteratorTraversingAndSplittingTest.java @@ -49,8 +49,10 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.PriorityQueue; +import java.util.RandomAccess; import java.util.Set; import java.util.SortedSet; import java.util.Spliterator; @@ -379,6 +381,150 @@ public class SpliteratorTraversingAndSplittingTest { db.addList(Vector::new); + class AbstractRandomAccessListImpl extends AbstractList implements RandomAccess { + Integer[] ia; + + AbstractRandomAccessListImpl(Collection c) { + this.ia = c.toArray(new Integer[c.size()]); + } + + @Override + public Integer get(int index) { + return ia[index]; + } + + @Override + public int size() { + return ia.length; + } + } + db.addList(AbstractRandomAccessListImpl::new); + + class RandomAccessListImpl implements List, RandomAccess { + Integer[] ia; + List l; + + RandomAccessListImpl(Collection c) { + this.ia = c.toArray(new Integer[c.size()]); + this.l = Arrays.asList(ia); + } + + @Override + public Integer get(int index) { + return ia[index]; + } + + @Override + public Integer set(int index, Integer element) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(int index, Integer element) { + throw new UnsupportedOperationException(); + } + + @Override + public Integer remove(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int indexOf(Object o) { + return l.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return Arrays.asList(ia).lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return l.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return l.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return l.subList(fromIndex, toIndex); + } + + @Override + public int size() { + return ia.length; + } + + @Override + public boolean isEmpty() { + return size() != 0; + } + + @Override + public boolean contains(Object o) { + return l.contains(o); + } + + @Override + public Iterator iterator() { + return l.iterator(); + } + + @Override + public Object[] toArray() { + return l.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return l.toArray(a); + } + + @Override + public boolean add(Integer integer) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + return l.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + } + db.addList(RandomAccessListImpl::new); db.addCollection(HashSet::new);