procesamiento - stream java 8 ejemplo
¿Cuándo debo usar transmisiones? (4)
El objetivo de las transmisiones en Java es simplificar la complejidad de escribir código paralelo. Está inspirado en la programación funcional. La transmisión en serie es solo para hacer que el código sea más limpio.
Si queremos rendimiento, debemos usar parallelStream, que fue diseñado para. El serial, en general, es más lento.
Hay un buen artículo para leer sobre
ForLoop
,
Stream
y
ParallelStream
Performance
.
En su código podemos usar métodos de terminación para detener la búsqueda en la primera coincidencia. (anyMatch ...)
Acabo de encontrar una pregunta cuando uso una
List
y su método
stream()
.
Si bien sé
cómo
usarlos, no estoy muy seguro de
cuándo
usarlos.
Por ejemplo, tengo una lista que contiene varias rutas a diferentes ubicaciones.
Ahora, me gustaría comprobar si una sola ruta dada contiene alguna de las rutas especificadas en la lista.
Me gustaría devolver un
boolean
función de si se cumplió o no la condición.
Esto, por supuesto, no es una tarea difícil per se. Pero me pregunto si debería usar streams o un bucle for (-each).
La lista
private static final List<String> EXCLUDE_PATHS = Arrays.asList(new String[]{
"my/path/one",
"my/path/two"
});
Ejemplo: transmisión
private boolean isExcluded(String path){
return EXCLUDE_PATHS.stream()
.map(String::toLowerCase)
.filter(path::contains)
.collect(Collectors.toList())
.size() > 0;
}
Ejemplo: para cada bucle
private boolean isExcluded(String path){
for (String excludePath : EXCLUDE_PATHS) {
if(path.contains(excludePath.toLowerCase())){
return true;
}
}
return false;
}
Tenga en cuenta
que el parámetro de
path
siempre está en
minúscula
.
Mi primera suposición es que el enfoque para cada uno es más rápido, porque el ciclo volvería inmediatamente, si se cumple la condición. Mientras que la secuencia todavía se repite en todas las entradas de la lista para completar el filtrado.
¿Es correcta mi suposición?
Si es así,
¿
por
qué
(o más bien
cuándo
) usaría
stream()
entonces?
La decisión de usar Streams o no no debe basarse en la consideración del rendimiento, sino en la legibilidad. Cuando realmente se trata de rendimiento, hay otras consideraciones.
Con su
.filter(path::contains).collect(Collectors.toList()).size() > 0
, está procesando todos los elementos y
.filter(path::contains).collect(Collectors.toList()).size() > 0
en una
List
temporal, antes de comparar el tamaño, aún así, esto casi nunca Importa para una secuencia que consta de dos elementos.
El uso de
.map(String::toLowerCase).anyMatch(path::contains)
puede ahorrar ciclos de CPU y memoria, si tiene un número de elementos sustancialmente mayor.
Aún así, esto convierte cada
String
en su representación en minúsculas, hasta que se encuentre una coincidencia.
Obviamente, hay un punto en usar
private static final List<String> EXCLUDE_PATHS =
Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
.collect(Collectors.toList());
private boolean isExcluded(String path) {
return EXCLUDE_PATHS.stream().anyMatch(path::contains);
}
en lugar.
Por lo tanto, no tiene que repetir la conversión a minúsculas en cada invocación de
isExcluded
.
Si el número de elementos en
EXCLUDE_PATHS
o la longitud de las cadenas es realmente grande, puede considerar usar
private static final List<Predicate<String>> EXCLUDE_PATHS =
Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
.map(s -> Pattern.compile(s, Pattern.LITERAL).asPredicate())
.collect(Collectors.toList());
private boolean isExcluded(String path){
return EXCLUDE_PATHS.stream().anyMatch(p -> p.test(path));
}
La compilación de una cadena como patrón de expresiones regulares con el indicador
LITERAL
hace que se comporte como las operaciones de cadena ordinarias, pero permite que el motor dedique un tiempo a la preparación, por ejemplo, utilizando el algoritmo Boyer Moore, para ser más eficiente cuando se trata de la comparación real.
Por supuesto, esto solo vale la pena si hay suficientes pruebas posteriores para compensar el tiempo dedicado a la preparación.
Determinar si este será el caso, es una de las consideraciones de rendimiento reales, además de la primera pregunta de si esta operación será crítica para el rendimiento.
No es la pregunta si usar Streams o
for
bucles.
Por cierto, los ejemplos de código anteriores mantienen la lógica de su código original, que me parece cuestionable.
Su método
isExcluded
devuelve
true
, si la ruta especificada contiene alguno de los elementos de la lista, por lo que devuelve
true
para
/some/prefix/to/my/path/one
, así como
my/path/one/and/some/suffix
o incluso
/some/prefix/to/my/path/one/and/some/suffix
.
Incluso
dummy/path/onerous
se considera que cumple con los criterios, ya que
contains
la cadena
my/path/one
...
Sí. Tienes razón. Su enfoque de transmisión tendrá algunos gastos generales. Pero puede usar tal construcción:
private boolean isExcluded(String path) {
return EXCLUDE_PATHS.stream().map(String::toLowerCase).anyMatch(path::contains);
}
La razón principal para usar transmisiones es que hacen que su código sea más simple y fácil de leer.
Tu suposición es correcta. La implementación de la transmisión es más lenta que el ciclo for.
Sin embargo, este uso de la transmisión debería ser tan rápido como el ciclo for:
EXCLUDE_PATHS.stream()
.map(String::toLowerCase)
.anyMatch(path::contains);
Esto itera a través de los elementos, aplicando
String::toLowerCase
y el filtro a los elementos uno por uno y
terminando en el primer elemento
que coincida.
Tanto
collect()
anyMatch()
son operaciones de terminal.
anyMatch()
sale del primer elemento encontrado, mientras que
collect()
requiere que se procesen todos los elementos.