Spliterator の characteristics メソッドが返す値まとめ #java
Java 8 で追加されたイテレータインターフェース Spliterator
は、そのデータソースに応じた characteristics を保持することができます。
どの特性を持つべきか自明でないケースがあると感じたので、標準ライブラリ経由で作成した Spliterator
が返す characteristics
の値をまとめてみました。
Spliterator is 何
java.util.Spliterator
は Java 8 で新しく追加されたイテレータインターフェースです。
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Spliterator.html
Java のイテレータといえば、古来より java.util.Iterator が存在します。
Spliterator
もおおよそはこの Iterator
と同じですが、より最適化された API を提供します。
tryAdvance
java.util.Iterator
の場合、hasNext
メソッドと next
メソッドを組み合わせて要素をイテレートします。
hasNext
で「次の」要素が存在するかチェックし、存在する場合は next
でその要素を取得するというプロトコルです。
Iterator<String> iterator = ……;
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
一方、Spliterator
は「次の」要素を取得するために tryAdvance
という単一のメソッドを提供します。
tryAdvance
は Spliterator
の要素を受け取り、何かを行う Consumer を引数に取ります。
「次の」要素が存在する場合はその要素を Consumer
に消化させつつ true
を返し、「次の」要素が存在しない場合は Consumer
を実行せず false
を返します。
Spliterator<String> spliterator = ……;
do { } while (spliterator.tryAdvance(System.out::println));
※単に全要素トラバースするのが目的であれば、java.util.Iterator
、java.util.Spliterator
ともに forEachRemaining
というメソッドを用いた方がよいと思います。
trySplit
インターフェース名にもその機能が見え隠れしていますが、Spliterator
はイテレータを分割するための trySplit
メソッドを提供します。
これはレシーバである Spliterator
の要素を、戻り値となる Spliterator
へ分割する API です。
通常単一の Iterator
や Spliterator
はスレッドセーフではありません。
しかし trySplit
で分割された各 Spliterator
はそれぞれ別のスレッドで処理できるので、結果として要素を並列にトラバーサルすることが可能となります。
他にも Spliterator
は要素数を取得できたり、後述する characteristics の指定ができたりなど、並列・分割統治を強く意識したインターフェースとなっています。
StreamSupport はこの trySplit
や、characteristics を用いてストリームを最適化するそうです。
例えば次のように配列から作成した Spliterator
の trySplit
を呼び出すと、それぞれの Spliterator
がちょうど半分ずつの要素を担当するように分割されます。
final int[] xs = {1, 2, 3, 4, 5, 6, 7, 8};
final Spliterator<Integer> a = Spliterators.spliterator(xs, 0);
final Spliterator<Integer> b = a.trySplit();
a.forEachRemaining(System.out::println); // 5, 6, 7, 8
b.forEachRemaining(System.out::println); // 1, 2, 3, 4
調べたかったこと
どのような特性を持つべきか
Spliterator
は自身が持つ特徴に基づいて、ヒントを格納することができます。
現在 Spliterator
が持つことができる特徴は「CONCURRENT」「DISTINCT」「IMMUTABLE」「NONNULL」「ORDERED」「SIZED」「SORTED」「SUBSIZED」の8つです。
意味は定数名通りで、例えば Set
から作られた Spliterator
は要素の重複がないことが保証されているから DISTINCT
属性を付与する、配列から作られた Spliterator
は要素数がわかっているから SIZED
属性を付与する、といった使い方をします。
ただ要素が1つであることが明白な場合に DISTINCT
属性や SORTED
属性を付与すべきかどうかが自明でなかったりなど、仕様に微妙に不安を感じました。
そこでさまざまな方法で Spliterator
を作成し、データ構造と特性の関係を眺めてみることにしました。
結果
調査ケースがかなり多いので、いくつかのパターンに分割して結果をまとめました。
それぞれ Spliterator
のソースと生成された Spliterator
の具象クラス、Spliterator
の characteristics を記載しています。
java.util.Iterator から作成
Spliterators.spliterator
と Spliterators.spliteratorUnknownSize
でそれぞれ作成してみました。
そのまんまの結果となりました。
ソース | Spliterator の具象クラス | Spliterator の characteristics |
---|---|---|
サイズ指定あり | java.util.Spliterators$IteratorSpliterator | SIZED, SUBSIZED |
サイズ指定なし | java.util.Spliterators$IteratorSpliterator | なし |
配列や通常のコレクションから作成
データ構造の特性が表現されているように見えます。
ただ、配列をソースとする Spliterator
に ORDERED
特性がない点は気になりました。
ソース | Spliterator の具象クラス | Spliterator の characteristics |
---|---|---|
配列 | java.util.Spliterators$ArraySpliterator | SIZED, SUBSIZED |
ArrayList | java.util.ArrayList$ArrayListSpliterator | ORDERED, SIZED, SUBSIZED |
LinkedList | java.util.LinkedList$LLSpliterator | ORDERED, SIZED, SUBSIZED |
HashSet | java.util.HashMap$KeySpliterator | DISTINCT, SIZED |
LinkedHashSet | java.util.Spliterators$IteratorSpliterator | DISTINCT, ORDERED, SIZED, SUBSIZED |
TreeSet | java.util.TreeMap$KeySpliterator | DISTINCT, ORDERED, SIZED, SORTED |
PriorityQueue | java.util.PriorityQueue$PriorityQueueSpliterator | NONNULL, SIZED, SUBSIZED |
並行コレクションから作成
ほぼすべてのパターンで、IMMUTABLE
もしくは CONCURRENT
特性が付与されています。
PriorityBlockingQueue
にそれらがないのは、ヒープのデータ構造に由来しているのだと思っています。
ソース | Spliterator の具象クラス | Spliterator の特性 |
---|---|---|
CopyOnWriteArrayList | java.util.Spliterators$ArraySpliterator | IMMUTABLE, ORDERED, SIZED, SUBSIZED |
CopyOnWriteArraySet | java.util.Spliterators$ArraySpliterator | DISTINCT, IMMUTABLE, SIZED, SUBSIZED |
ConcurrentSkipListSet | java.util.concurrent.ConcurrentSkipListMap$KeySpliterator | CONCURRENT, DISTINCT, NONNULL, ORDERED, SORTED |
ConcurrentLinkedQueue | java.util.concurrent.ConcurrentLinkedQueue$CLQSpliterator | CONCURRENT, NONNULL, ORDERED |
LinkedBlockingQueue | java.util.concurrent.LinkedBlockingQueue$LBQSpliterator | CONCURRENT, NONNULL, ORDERED |
LinkedTransferQueue | java.util.concurrent.LinkedTransferQueue$LTQSpliterator | CONCURRENT, NONNULL, ORDERED |
PriorityBlockingQueue | java.util.concurrent.PriorityBlockingQueue$PBQSpliterator | NONNULL, SIZED, SUBSIZED |
特殊な制約のあるパターン
Collections
クラスを使って作成した不変シングルトンリストや、不変空リストなどを試してみました。
不変空コレクションの characteristics は正直色々足りていない気がします。
ソース | Spliterator の具象クラス | Spliterator の特性 |
---|---|---|
Collections.singletonList | java.util.Collections$2 | DISTINCT, IMMUTABLE, NONNULL, ORDERED, SIZED, SUBSIZED |
Collections.singleton | java.util.Collections$2 | DISTINCT, IMMUTABLE, NONNULL, ORDERED, SIZED, SUBSIZED |
Collections.emptyList | java.util.Spliterators$EmptySpliterator$OfRef | SIZED, SUBSIZED |
Collections.emptySet | java.util.Spliterators$EmptySpliterator$OfRef | SIZED, SUBSIZED |
Collections.emptySortedSet | java.util.TreeMap$KeySpliterator | DISTINCT, ORDERED, SIZED, SORTED |
Spliterators.emptySpliterator | java.util.Spliterators$EmptySpliterator$OfRef | SIZED, SUBSIZED |
まとめ
様々な方法で Spliterator
を作成し、それぞれの characteristics を取得してみました。
おおよその傾向はつかめたものの、標準ライブラリ自体、必ずしも一貫した実装となっているわけではなさそうです。
調査履歴
調査ログです。
長いので結果まとめの参照をオススメします。
Java は「Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60」です。
前提
Scala REPL で試しました。
あらかじめ次のインポートとメソッド定義を行っておきます。
import java.util._
import java.util.concurrent._
def printCharacteristics[T](spliterator: Spliterator[T]): Unit = {
println(Seq(
if (spliterator.hasCharacteristics(Spliterator.CONCURRENT)) Some("CONCURRENT") else None,
if (spliterator.hasCharacteristics(Spliterator.DISTINCT)) Some("DISTINCT") else None,
if (spliterator.hasCharacteristics(Spliterator.IMMUTABLE)) Some("IMMUTABLE") else None,
if (spliterator.hasCharacteristics(Spliterator.NONNULL)) Some("NONNULL") else None,
if (spliterator.hasCharacteristics(Spliterator.ORDERED)) Some("ORDERED") else None,
if (spliterator.hasCharacteristics(Spliterator.SIZED)) Some("SIZED") else None,
if (spliterator.hasCharacteristics(Spliterator.SORTED)) Some("SORTED") else None,
if (spliterator.hasCharacteristics(Spliterator.SUBSIZED)) Some("SUBSIZED") else None
).flatten.mkString(", "))
}
java.util.Iterator から作成
サイズ指定あり
Spliterators.spliterator
の第二引数はイテレータのサイズ、第三引数は追加で指定する characteristics です。
例えば要素が null
でないことがアプリケーションレベルで保証されている場合、第三引数に NONNULL
を指定する、といった使い方ができます。
0を指定すると何も characteristics を追加しません。
scala> val iterator = new java.util.Iterator[Int] {
| var i = 0
| def hasNext(): Boolean = i < 8
| def next(): Int = {
| if (i >= 8) throw new NoSuchElementException()
| i += 1
| i
| }
| }
iterator: java.util.Iterator[Int]{def i: Int; def i_=(x$1: Int): Unit} = $anon$1@1d6dc2b8
scala> val spliterator = Spliterators.spliterator(iterator, 8, 0)
spliterator: java.util.Spliterator[Int] = java.util.Spliterators$IteratorSpliterator@5c8e7687
scala> printCharacteristics(spliterator)
SIZED, SUBSIZED
サイズ指定なし
イテレータはあらかじめ要素数が確定していなかったり、あるいは無限の要素を持つ可能性があります。
そのような場合、Spliterators.spliteratorUnknownSize
を用いて Spliterator
を作成します。
第二引数はは追加で指定する characteristics です
scala> val iterator = new java.util.Iterator[Int] {
| var i = 0
| def hasNext(): Boolean = i < 8
| def next(): Int = {
| if (i >= 8) throw new NoSuchElementException()
| i += 1
| i
| }
| }
iterator: java.util.Iterator[Int]{def i: Int; def i_=(x$1: Int): Unit} = $anon$1@1c96bf1e
scala> val spliterator = Spliterators.spliteratorUnknownSize(iterator, 0)
spliterator: java.util.Spliterator[Int] = java.util.Spliterators$IteratorSpliterator@44ba9f25
scala> printCharacteristics(spliterator)
配列や通常のコレクションから作成
配列
Array[Int]
からそのまま Spliterator
を作るとプリミティブ配列に特化した Spliterator
ができるので、AnyRef
(Java の Object
型)にキャストしています。
scala> val spliterator = Spliterators.spliterator[Int]((1 to 8).toArray.map(_.asInstanceOf[AnyRef]), 0)
spliterator: java.util.Spliterator[Int] = java.util.Spliterators$ArraySpliterator@32ca397b
scala> printCharacteristics(spliterator)
SIZED, SUBSIZED
ArrayList
scala> val collection = new ArrayList[Int]()
collection: java.util.ArrayList[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.ArrayList$ArrayListSpliterator@7d3af2f0
scala> printCharacteristics(spliterator)
ORDERED, SIZED, SUBSIZED
LinkedList
scala> val collection = new LinkedList[Int]()
collection: java.util.LinkedList[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.LinkedList$LLSpliterator@71637a85
scala> printCharacteristics(spliterator)
ORDERED, SIZED, SUBSIZED
HashSet
scala> val collection = new HashSet[Int]()
collection: java.util.HashSet[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.HashMap$KeySpliterator@71daf5d6
scala> printCharacteristics(spliterator)
DISTINCT, SIZED
LinkedHashSet
scala> val collection = new LinkedHashSet[Int]()
collection: java.util.LinkedHashSet[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.Spliterators$IteratorSpliterator@28391cc6
scala> printCharacteristics(spliterator)
DISTINCT, ORDERED, SIZED, SUBSIZED
TreeSet
scala> val collection = new TreeSet[Int]()
collection: java.util.TreeSet[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.TreeMap$KeySpliterator@62b630e0
scala> printCharacteristics(spliterator)
DISTINCT, ORDERED, SIZED, SORTED
PriorityQueue
scala> val collection = new PriorityQueue[Int]()
collection: java.util.PriorityQueue[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.PriorityQueue$PriorityQueueSpliterator@6f75d11b
scala> printCharacteristics(spliterator)
NONNULL, SIZED, SUBSIZED
並行コレクション
CopyOnWriteArrayList
scala> val collection = new CopyOnWriteArrayList[Int]()
collection: java.util.concurrent.CopyOnWriteArrayList[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.Spliterators$ArraySpliterator@5a931c02
scala> printCharacteristics(spliterator)
IMMUTABLE, ORDERED, SIZED, SUBSIZED
CopyOnWriteArraySet
scala> val collection = new CopyOnWriteArraySet[Int]()
collection: java.util.concurrent.CopyOnWriteArraySet[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.Spliterators$ArraySpliterator@ca024d8
scala> printCharacteristics(spliterator)
DISTINCT, IMMUTABLE, SIZED, SUBSIZED
ConcurrentSkipListSet
scala> val collection = new ConcurrentSkipListSet[Int]()
collection: java.util.concurrent.ConcurrentSkipListSet[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.concurrent.ConcurrentSkipListMap$KeySpliterator@7e003cf0
scala> printCharacteristics(spliterator)
CONCURRENT, DISTINCT, NONNULL, ORDERED, SORTED
ConcurrentLinkedQueue
scala> val collection = new ConcurrentLinkedQueue[Int]()
collection: java.util.concurrent.ConcurrentLinkedQueue[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.concurrent.ConcurrentLinkedQueue$CLQSpliterator@ffbdb79
scala> printCharacteristics(spliterator)
CONCURRENT, NONNULL, ORDERED
LinkedBlockingQueue
scala> val collection = new LinkedBlockingQueue[Int]()
collection: java.util.concurrent.LinkedBlockingQueue[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.concurrent.LinkedBlockingQueue$LBQSpliterator@d57ba2a
scala> printCharacteristics(spliterator)
CONCURRENT, NONNULL, ORDERED
LinkedTransferQueue
scala> val collection = new LinkedTransferQueue[Int]()
collection: java.util.concurrent.LinkedTransferQueue[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.concurrent.LinkedTransferQueue$LTQSpliterator@2977084e
scala> printCharacteristics(spliterator)
CONCURRENT, NONNULL, ORDERED
PriorityBlockingQueue
scala> val collection = new PriorityBlockingQueue[Int]()
collection: java.util.concurrent.PriorityBlockingQueue[Int] = []
scala> (1 to 8).foreach(collection.add)
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.concurrent.PriorityBlockingQueue$PBQSpliterator@1fd97710
scala> printCharacteristics(spliterator)
NONNULL, SIZED, SUBSIZED
特殊な制約のあるパターン
Collections.singletonList
要素が1つしかない不変リストです。
scala> val collection = Collections.singletonList(1)
collection: java.util.List[Int] = [1]
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.Collections$2@56625ce9
scala> printCharacteristics(spliterator)
DISTINCT, IMMUTABLE, NONNULL, ORDERED, SIZED, SUBSIZED
Collections.singlton
要素が1つしかない不変セットです。
scala> val collection = Collections.singleton(1)
collection: java.util.Set[Int] = [1]
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.Collections$2@60d501f6
scala> printCharacteristics(spliterator)
DISTINCT, IMMUTABLE, NONNULL, ORDERED, SIZED, SUBSIZED
Collections.emptyList
不変空リストです。
scala> val collection = Collections.emptyList[Int]()
collection: java.util.List[Int] = []
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.Spliterators$EmptySpliterator$OfRef@170f6972
scala> printCharacteristics(spliterator)
SIZED, SUBSIZED
Collections.emptySet
不変空セットです。
scala> val collection = Collections.emptySet[Int]()
collection: java.util.Set[Int] = []
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.Spliterators$EmptySpliterator$OfRef@170f6972
scala> printCharacteristics(spliterator)
SIZED, SUBSIZED
Collections.emptySortedSet
不変空 SortedSet です。
scala> val collection = Collections.emptySortedSet[Int]()
collection: java.util.SortedSet[Int] = []
scala> val spliterator = collection.spliterator()
spliterator: java.util.Spliterator[Int] = java.util.TreeMap$KeySpliterator@d5ce7bb
scala> printCharacteristics(spliterator)
DISTINCT, ORDERED, SIZED, SORTED
Spliterators.emptySpliterator
空 Spliterator
です。
scala> val spliterator = Spliterators.emptySpliterator[Int]()
spliterator: java.util.Spliterator[Int] = java.util.Spliterators$EmptySpliterator$OfRef@170f6972
scala> printCharacteristics(spliterator)
SIZED, SUBSIZED