web-dev-qa-db-fra.com

Quand devrais-je utiliser des flux?

Je viens de tomber sur une question lorsque j'utilise une méthode List et sa méthode stream(). Bien que je sache comment les utiliser, je ne suis pas tout à fait sûr de quand les utiliser.

Par exemple, j'ai une liste contenant divers chemins d'accès à différents endroits. Maintenant, j'aimerais vérifier si un seul chemin donné contient l'un des chemins spécifiés dans la liste. Je voudrais retourner un boolean basé sur si la condition a été remplie ou non.

Bien entendu, ce n’est pas une tâche difficile en soi. Mais je me demande si je devrais utiliser des flux ou une boucle for (-each).

La liste

private static final List<String> EXCLUDE_PATHS = Arrays.asList(new String[]{
    "my/path/one",
    "my/path/two"
});

Exemple - Stream

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream()
                        .map(String::toLowerCase)
                        .filter(path::contains)
                        .collect(Collectors.toList())
                        .size() > 0;
}

Exemple - Pour chaque boucle

private boolean isExcluded(String path){
    for (String excludePath : EXCLUDE_PATHS) {
        if(path.contains(excludePath.toLowerCase())){
            return true;
        }
    }
    return false;
}

Note que le paramètre path est toujours en minuscule .

Ma première hypothèse est que l'approche pour chaque est plus rapide, car la boucle reviendrait immédiatement si la condition est remplie. Considérant que le flux serait toujours en boucle sur toutes les entrées de la liste afin de terminer le filtrage.

Est-ce que mon hypothèse est correcte? Si oui, pourquoi (ou plutôt quand ) utiliserais-je stream() alors?

86
mcuenez

Votre hypothèse est correcte. L'implémentation de votre flux est plus lente que la boucle for.

Cette utilisation de flux devrait être aussi rapide que la boucle for cependant:

EXCLUDE_PATHS.stream()  
                               .map(String::toLowerCase)
                               .anyMatch(path::contains);

Cela itère à travers les éléments, en appliquant String::toLowerCase et le filtre aux éléments un à un et se terminant au premier élément qui correspond.

Les deux collect() & anyMatch() sont des opérations terminales. anyMatch() quitte au premier élément trouvé, alors que collect() nécessite le traitement de tous les éléments.

70
Stefan Pries

La décision d'utiliser ou non les Streams ne devrait pas être motivée par des considérations de performances, mais plutôt par la lisibilité. En matière de performance, il y a d'autres considérations.

Avec votre approche .filter(path::contains).collect(Collectors.toList()).size() > 0, vous traitez tous les éléments et vous les collectez dans un List temporaire, avant de comparer la taille, cela n'a pourtant guère d'importance pour un Stream composé de deux éléments.

L'utilisation de .map(String::toLowerCase).anyMatch(path::contains) peut économiser des cycles de calcul et de la mémoire si vous avez un nombre d'éléments beaucoup plus important. Néanmoins, ceci convertit chaque String en sa représentation en minuscule, jusqu'à ce qu'une correspondance soit trouvée. De toute évidence, il y a un point en utilisant

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);
}

au lieu. Il n’est donc pas nécessaire de répéter la conversion en lowcase à chaque invocation de isExcluded. Si le nombre d'éléments dans EXCLUDE_PATHS ou les longueurs des chaînes deviennent vraiment grands, vous pouvez envisager d'utiliser

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 compilation d'une chaîne en tant que motif regex avec l'indicateur LITERAL lui permet de se comporter comme des opérations de chaîne ordinaires, tout en permettant au moteur de passer un certain temps en préparation, par exemple. en utilisant l'algorithme de Boyer Moore, pour être plus efficace quand il s'agit de la comparaison réelle.

Bien entendu, cela ne porte ses fruits que s’il ya suffisamment de tests ultérieurs pour compenser le temps consacré à la préparation. Déterminer si tel sera le cas, est l’un des aspects à prendre en compte en matière de performances, outre la première question de savoir si cette opération sera un jour critique en termes de performances. Pas la question d'utiliser des flux ou des boucles for.

En passant, les exemples de code ci-dessus conservent la logique de votre code d'origine, ce qui me semble douteux. Votre méthode isExcluded retourne true, si le chemin spécifié contient l'un des éléments de la liste, elle renvoie donc true pour /some/prefix/to/my/path/one, ainsi que my/path/one/and/some/suffix ou même /some/prefix/to/my/path/one/and/some/suffix.

Même dummy/path/onerous est considéré comme remplissant les critères car il contains la chaîne my/path/one

31
Holger

Ouais. Tu as raison. Votre approche de flux aura des frais généraux. Mais vous pouvez utiliser une telle construction:

private boolean isExcluded(String path) {
    return  EXCLUDE_PATHS.stream().map(String::toLowerCase).anyMatch(path::contains);
}

La principale raison d'utiliser des flux est qu'ils rendent votre code plus simple et facile à lire.

19
rvit34

Le but des flux dans Java est de simplifier la complexité d'écriture de code parallèle. C'est inspiré par la programmation fonctionnelle. Le flux série sert uniquement à rendre le code plus propre.

Si nous voulons des performances, nous devrions utiliser parallelStream, qui a été conçu pour. Le numéro de série, en général, est plus lent.

Il existe un bon article à lire sur ForLoop, Stream et ParallelStream Performance .

Dans votre code, nous pouvons utiliser des méthodes de terminaison pour arrêter la recherche lors de la première correspondance. (anyMatch ...)

8
Paulo Ricardo Almeida