java - parallelism - Comprender las características del spliterador profundo
java parallelism (1)
Tengo que admitir que también tuve dificultades cuando intenté por primera vez averiguar el significado real de las características y tuve la sensación de que su significado no se resolvió claramente durante la fase de implementación de Java 8 y se usa de manera inconsistente por esa razón.
Considere
Spliterator.IMMUTABLE
.
Spliterator.IMMUTABLE
:
Valor característico que significa que la fuente del elemento no puede modificarse estructuralmente; es decir, los elementos no se pueden agregar, reemplazar o eliminar, por lo que dichos cambios no pueden ocurrir durante el recorrido.
Es extraño ver "reemplazado" en esta lista, que generalmente no se considera una modificación estructural cuando se habla de una
List
o una matriz y, en consecuencia, las fábricas de flujo y spliterator que aceptan una matriz (que no está clonada) informan
IMMUTABLE
, como
LongStream.of(…)
o
Arrays.spliterator(long[])
.
Si interpretamos esto de manera más generosa como "siempre que el cliente no pueda observarlo", no existe una diferencia significativa con
CONCURRENT
, ya que en cualquier caso
algunos
elementos se informarán al cliente sin ninguna forma de reconocer si se agregaron durante el recorrido o si algunos no se informaron debido a la eliminación, ya que no hay forma de rebobinar un spliterator y comparar.
La especificación continúa:
Se espera que un Spliterator que no informa
IMMUTABLE
oCONCURRENT
tenga una política documentada (por ejemplo, lanzarConcurrentModificationException
) con respecto a la interferencia estructural detectada durante el recorrido.
Y eso es lo único relevante, un spliterator que informa, ya sea
IMMUTABLE
o
CONCURRENT
, tiene la garantía de nunca lanzar una
ConcurrentModificationException
.
Por supuesto,
CONCURRENT
excluye
SIZED
semántico, pero eso no tiene ninguna consecuencia para el código del cliente.
De hecho, estas características no se usan para nada en la API de Stream, por lo tanto, usarlas de manera inconsistente nunca se notaría en ningún lado.
Esta es también la explicación de por qué
cada
operación intermedia tiene el efecto de borrar las características
CONCURRENT
,
IMMUTABLE
y
NONNULL
: la implementación de Stream no las usa y sus clases internas que representan el estado de stream no las mantienen.
Del mismo modo,
NONNULL
no se usa en ninguna parte, por lo que su ausencia para ciertas transmisiones no tiene ningún efecto.
Podría rastrear el problema
LongStream.of(…)
hasta el uso interno de
Arrays.spliterator(long[], int, int)
que delega a
Spliterators.spliterator(long[] array, int fromIndex, int toIndex, int additionalCharacteristics)
:
El spliterator devuelto siempre informa las características
SUBSIZED
ySUBSIZED
. La persona que llama puede proporcionar características adicionales para que el divisor informe. (Por ejemplo, si se sabe que la matriz no se modificará más, especifiqueIMMUTABLE
; si se considera que los datos de la matriz tienen un orden de encuentro, especifiqueORDERED
). El métodoArrays.spliterator(long[], int, int)
menudo se puede usar en su lugar, lo que devuelve un spliterator que informaSUBSIZED
,SUBSIZED
,IMMUTABLE
yORDERED
.
Tenga en cuenta (nuevamente) el uso inconsistente de la característica
IMMUTABLE
.
Se trata nuevamente como si tuviera que garantizar la ausencia de cualquier modificación, mientras que al mismo tiempo,
Arrays.spliterator
y, a su vez,
Arrays.stream
y
LongStream.of(…)
informarán la característica
IMMUTABLE
, incluso por especificación, sin poder Garantía de que la persona que llama no modificará su matriz.
A menos que consideremos establecer un elemento que no sea una modificación estructural, pero entonces, la distinción completa se vuelve absurda nuevamente, ya que las matrices no pueden modificarse estructuralmente.
Y claramente no especificó ninguna característica
NONNULL
.
Si bien es obvio que los valores primitivos no pueden ser
null
, y el
Spliterator.Abstract<Primitive>Spliterator
inyecta invariablemente una característica
NONNULL
, el spliterator devuelto por
Spliterators.spliterator(long[],int,int,int)
no heredar de
Spliterator.AbstractLongSpliterator
.
Lo malo es que esto no se puede solucionar sin cambiar la especificación, lo bueno es que no tiene consecuencias de todos modos.
Entonces, si ignoramos cualquier problema con
CONCURRENT
,
IMMUTABLE
o
NONNULL
, que no tiene consecuencias, tenemos
SIZED
y
skip
y
limit
.
Este es un problema bien conocido, resultado de la forma en que Stream API ha implementado la
skip
y el
limit
.
Otras implementaciones son imaginables.
Esto también se aplica a la combinación de un flujo infinito con un
limit
, que debe tener un tamaño predecible, pero dada la implementación actual, no lo tiene.
Combinando
Stream.concat(…)
con
Stream.empty()
.
Parece razonable que una secuencia vacía
no
imponga restricciones en el orden del resultado.
Pero el
Stream.concat(…)
de liberar el orden cuando solo una entrada no tiene orden, es cuestionable.
Tenga en cuenta que ser demasiado agresivo con respecto al pedido no es nada nuevo, vea
estas preguntas y respuestas
sobre un comportamiento que se consideró intencional primero, pero luego se ha solucionado tan tarde como Java 8, actualización 60. Tal vez,
Stream.concat
debería haberse discutido en este momento de tiempo también ...
El comportamiento de
.boxed()
es fácil de explicar.
Cuando se ha implementado ingenuamente como
.mapToObj(Long::valueOf)
, simplemente perderá todo el conocimiento, ya que
mapToObj
no puede asumir que el resultado todavía está ordenado o es distinto.
Pero esto se ha solucionado con Java 9. Allí,
LongStream.range(0,10).boxed()
tiene
SUBSIZED|SIZED|ORDERED|SORTED|DISTINCT
características, manteniendo todas las características que son relevantes para la implementación.
Con el fin de tratar de comprender profundamente las transmisiones Java y los spliteradores, tengo algunas preguntas sutiles sobre las características del spliterator :
Q1:
Stream.empty()
vs
Stream.of()
(Stream.of () sin argumentos)
-
Stream.empty()
: SUBSIZADO, TAMAÑO -
Stream.of()
: SUBSIZADO, INMUTABLE , TAMAÑO, ORDENADO
¿Por qué
Stream.empty()
no tiene las mismas características de
Stream.of()
?
Tenga en cuenta que tiene impactos cuando se usa junto con Stream.concat () (especialmente sin haber
ORDERED
).
Yo diría que
Stream.empty()
no solo debe haber sido
INMUTABLE y ORDENADO,
sino también
DISTINCT y NONNULL
.
También tiene sentido
Stream.of()
con solo un argumento que tiene
DISTICT
.
Q2:
LongStream.of()
no tiene
NONNULL
Acabo de notar que NONNULL no está disponible en
LongStream.of
.
¿No es
NONNULL
una característica principal de todos los
LongStream
s,
IntStream
s y
DoubleStream
s?
Q3:
LongStream.range(,)
vs
LongStream.range(,).boxed()
-
LongRange.range(,)
: SUBSIZADO, INMUTABLE, NO NULL , TAMAÑO , ORDENADO , CLASIFICADO , DISTINTO -
LongStream.range(,).boxed()
BoxedLongStream.range(,).boxed()
: SUBSIZED, SIZED, ORDERED
¿Por qué
.boxed()
pierde todas estas características?
No debería perder ninguno.
Entiendo que
.mapToObj()
puede perder
NONNULL, INMUTABLE y DISTICT
, pero
.boxed()
... no tiene sentido.
P4:
.peek()
pierde
INMUTABLE y NO NULL
LongStream.of(1)
:
SUBSIZADO, INMUTABLE, NO NULL, TAMAÑO, ...
LongStream.of(1).peek()
:
SUBSIZADO, TAMAÑO, ...
¿Por qué
.peek()
pierde estas características?
.peek
realmente no debería perder ninguno.
Q5:
.skip()
,
.limit()
pierde
SUBSIZED, INMUTABLE, NONULL, SIZED
Solo observe que estas operaciones pierden SUBSIZADO, INMUTABLE, NO NULL, TAMAÑO . ¿Por qué? Si el tamaño está disponible, entonces también es fácil calcular el tamaño final.
Q6:
.filter()
pierde
INMUTABLE, NO NULL
Solo tenga en cuenta que esta operación también pierde SUBSIZADO, INMUTABLE, NO NULL, TAMAÑO . Tiene sentido perder SUBSIZADO y TAMAÑO , pero los otros dos no tienen sentido. ¿Por qué?
Apreciaré si alguien que entiende profundamente el separador podría aportar algo de claridad. Gracias.