Java: Si vs Commutateur


J'ai un morceau de code avec a) que j'ai remplacé par b) uniquement pour la lisibilité ...

A)

if ( WORD[ INDEX ] == 'A' ) branch = BRANCH.A;
/* B through to Y */
if ( WORD[ INDEX ] == 'Z' ) branch = BRANCH.Z;

B)

switch ( WORD[ INDEX ] ) {
    case 'A' : branch = BRANCH.A; break;
    /* B through to Y */
    case 'Z' : branch = BRANCH.Z; break;
}


... la version switch va-t-elle passer en cascade à travers toutes les permutations ou passer à un cas ?



MODIFIER:

Quelques réponses ci-dessous l'égard des approches alternatives à l'approche ci-dessus.
J'ai inclus ce qui suit pour fournir un contexte pour son utilisation.

La raison pour laquelle j'ai posé, la question ci-dessus, était parce que la vitesse d'ajout de mots s'améliorait empiriquement.

Ce n'est en aucun cas du code de production et a été piraté rapidement en tant que PoC.

Ce qui suit semble être une confirmation de l'échec d'une expérience de pensée.
J'ai peut-être besoin d'un corpus de mots beaucoup plus grand que celui que j'utilise actuellement.
L'échec provient du fait que je n'ai pas tenu compte les références null nécessitant toujours de la mémoire. (doh ! )

public class Dictionary {
    private static Dictionary ROOT;
    private boolean terminus;
    private Dictionary A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z;
    private static Dictionary instantiate( final Dictionary DICTIONARY ) {
        return ( DICTIONARY == null ) ? new Dictionary() : DICTIONARY;
    }
    private Dictionary() {
        this.terminus = false;
        this.A = this.B = this.C = this.D = this.E = this.F = this.G = this.H = this.I = this.J = this.K = this.L = this.M = this.N = this.O = this.P = this.Q = this.R = this.S = this.T = this.U = this.V = this.W = this.X = this.Y = this.Z = null;
    }
    public static void add( final String...STRINGS ) {
        Dictionary.ROOT = Dictionary.instantiate( Dictionary.ROOT );
        for ( final String STRING : STRINGS ) Dictionary.add( STRING.toUpperCase().toCharArray(), Dictionary.ROOT , 0, STRING.length() - 1 );
    }
    private static void add( final char[] WORD, final Dictionary BRANCH, final int INDEX, final int INDEX_LIMIT ) {
        Dictionary branch = null;
        switch ( WORD[ INDEX ] ) {
        case 'A' : branch = BRANCH.A = Dictionary.instantiate( BRANCH.A ); break;
        case 'B' : branch = BRANCH.B = Dictionary.instantiate( BRANCH.B ); break;
        case 'C' : branch = BRANCH.C = Dictionary.instantiate( BRANCH.C ); break;
        case 'D' : branch = BRANCH.D = Dictionary.instantiate( BRANCH.D ); break;
        case 'E' : branch = BRANCH.E = Dictionary.instantiate( BRANCH.E ); break;
        case 'F' : branch = BRANCH.F = Dictionary.instantiate( BRANCH.F ); break;
        case 'G' : branch = BRANCH.G = Dictionary.instantiate( BRANCH.G ); break;
        case 'H' : branch = BRANCH.H = Dictionary.instantiate( BRANCH.H ); break;
        case 'I' : branch = BRANCH.I = Dictionary.instantiate( BRANCH.I ); break;
        case 'J' : branch = BRANCH.J = Dictionary.instantiate( BRANCH.J ); break;
        case 'K' : branch = BRANCH.K = Dictionary.instantiate( BRANCH.K ); break;
        case 'L' : branch = BRANCH.L = Dictionary.instantiate( BRANCH.L ); break;
        case 'M' : branch = BRANCH.M = Dictionary.instantiate( BRANCH.M ); break;
        case 'N' : branch = BRANCH.N = Dictionary.instantiate( BRANCH.N ); break;
        case 'O' : branch = BRANCH.O = Dictionary.instantiate( BRANCH.O ); break;
        case 'P' : branch = BRANCH.P = Dictionary.instantiate( BRANCH.P ); break;
        case 'Q' : branch = BRANCH.Q = Dictionary.instantiate( BRANCH.Q ); break;
        case 'R' : branch = BRANCH.R = Dictionary.instantiate( BRANCH.R ); break;
        case 'S' : branch = BRANCH.S = Dictionary.instantiate( BRANCH.S ); break;
        case 'T' : branch = BRANCH.T = Dictionary.instantiate( BRANCH.T ); break;
        case 'U' : branch = BRANCH.U = Dictionary.instantiate( BRANCH.U ); break;
        case 'V' : branch = BRANCH.V = Dictionary.instantiate( BRANCH.V ); break;
        case 'W' : branch = BRANCH.W = Dictionary.instantiate( BRANCH.W ); break;
        case 'X' : branch = BRANCH.X = Dictionary.instantiate( BRANCH.X ); break;
        case 'Y' : branch = BRANCH.Y = Dictionary.instantiate( BRANCH.Y ); break;
        case 'Z' : branch = BRANCH.Z = Dictionary.instantiate( BRANCH.Z ); break;
        }   
        if ( INDEX == INDEX_LIMIT ) branch.terminus = true;
        else Dictionary.add( WORD, branch, INDEX + 1, INDEX_LIMIT );
    }
    public static boolean is( final String STRING ) {
        Dictionary.ROOT = Dictionary.instantiate( Dictionary.ROOT );
        return Dictionary.is( STRING.toUpperCase().toCharArray(), Dictionary.ROOT, 0, STRING.length() - 1 );
    }
    private static boolean is( final char[] WORD, final Dictionary BRANCH, final int INDEX, final int INDEX_LIMIT ) {
        Dictionary branch = null;
        switch ( WORD[ INDEX ] ) {
        case 'A' : branch = BRANCH.A; break;
        case 'B' : branch = BRANCH.B; break;
        case 'C' : branch = BRANCH.C; break;
        case 'D' : branch = BRANCH.D; break;
        case 'E' : branch = BRANCH.E; break;
        case 'F' : branch = BRANCH.F; break;
        case 'G' : branch = BRANCH.G; break;
        case 'H' : branch = BRANCH.H; break;
        case 'I' : branch = BRANCH.I; break;
        case 'J' : branch = BRANCH.J; break;
        case 'K' : branch = BRANCH.K; break;
        case 'L' : branch = BRANCH.L; break;
        case 'M' : branch = BRANCH.M; break;
        case 'N' : branch = BRANCH.N; break;
        case 'O' : branch = BRANCH.O; break;
        case 'P' : branch = BRANCH.P; break;
        case 'Q' : branch = BRANCH.Q; break;
        case 'R' : branch = BRANCH.R; break;
        case 'S' : branch = BRANCH.S; break;
        case 'T' : branch = BRANCH.T; break;
        case 'U' : branch = BRANCH.U; break;
        case 'V' : branch = BRANCH.V; break;
        case 'W' : branch = BRANCH.W; break;
        case 'X' : branch = BRANCH.X; break;
        case 'Y' : branch = BRANCH.Y; break;
        case 'Z' : branch = BRANCH.Z; break;
        }
        if ( branch == null ) return false;
        if ( INDEX == INDEX_LIMIT ) return branch.terminus;
        else return Dictionary.is( WORD, branch, INDEX + 1, INDEX_LIMIT );
    }
}
Author: BalusC, 2009-06-30

11 answers

Dans le bytecode, il existe deux formes de commutateur: tableswitch et lookupswitch. L'un suppose un ensemble dense de clés, l'autre clairsemé. Voir la description du commutateur de compilation dans la spécification JVM. Pour les énumérations, l'ordinal est trouvé, puis le code continue comme le cas int. Je ne suis pas tout à fait sûr de la façon dont la petite fonctionnalité switch sur String proposée dans JDK7 sera implémentée.

Cependant, le code fortement utilisé est généralement compilé dans n'importe quelle JVM sensée. L'optimiseur n'est pas totalement stupide. Ne vous inquiétez pas à ce sujet, et suivez les heuristiques habituelles pour l'optimisation.

 21
Author: Tom Hawtin - tackline, 2009-06-29 23:49:21

Ne vous inquiétez pas des performances; utilisez la syntaxe qui exprime le mieux ce que vous faites. Seulement après avoir (a) démontré un déficit de performance; et (b) localisé à la routine en question, alors seulement devriez-vous vous soucier de la performance. Pour mon argent, la syntaxe de cas est plus appropriée ici.

 24
Author: Carl Manaster, 2009-06-29 23:46:13

Il semble que vous ayez énuméré les valeurs, donc peut-être qu'une énumération est en ordre?

enum BRANCH {
  A,B, ... Y,Z;
}

, Puis dans votre code :

BRANCH branch = BRANCH.valueOf( WORD[ INDEX ] );

De plus, il y a un bogue possible dans votre code où "A" == "A" peut être faux en fonction de l'identité de l'objet des "A".

 7
Author: Clint, 2009-06-30 03:29:52

Pas tout à fait une réponse à votre question, mais il y a en fait un bug dans votre code, vous devriez avoir une pause après chaque cas:

switch ( WORD[ INDEX ] ) {
    case 'A' : branch = BRANCH.A; break;
    /* B through to Y */
    case 'Z' : branch = BRANCH.Z; break;
}

Je ne pense pas que les différences de performances seront trop importantes ici, mais si vous vous souciez vraiment des performances, et si ce code est exécuté très fréquemment, voici une autre option:

// Warning, untested code.
BRANCH[] branchLookUp = {BRANCH.A, BRANCH.B, ..., BRANCH.Z};

branch = branchLookUp[WORD[INDEX] - 'A'];

Assurez-vous d'encapsuler cela et de bien le documenter.

 4
Author: Jack Leow, 2009-06-29 23:57:45

Honnêtement, je ne pense pas que la performance compte dans ce cas. C'est vraiment au compilateur et à son optimisation.

 3
Author: Cambium, 2009-06-29 23:47:16

Si vous avez une instruction switch avec des valeurs intégrales consécutives, selon la langue, elle peut être optimisée en une table de branches, ce qui est très rapide. Ce n'est pas plus lent, de toute façon!

De plus, l'utilisation de if/else if serait une amélioration par rapport à if, pour des cas tels que celui-ci dans lesquels vos cas sont mutuellement exclusifs. Pas de sens de faire 25 contrôles supplémentaires après avoir fait correspondre A.

, Mais, fondamentalement, toute différence de performances est négligeable, et vous devez utiliser la syntaxe, qui dans ce cas est l'instruction switch. Assurez-vous de séparer vos cas avec des pauses cependant.

 3
Author: Nick Lewis, 2009-06-29 23:48:07

Voici un moyen d'éviter toutes les déclarations de cas.

import java.util.HashMap;

public class Dictionary {
    private static Dictionary                       ROOT;
    private boolean                                 terminus;
    private final HashMap<Character, Dictionary>    dictionaries    = new HashMap<Character, Dictionary>();

    private void ensureBranch(char c) {
        if (getBranch(c) != null)
            return;
        dictionaries.put(c, new Dictionary());
    }

    private Dictionary getBranch(char c) {
        return dictionaries.get(c);
    }

    public static boolean is(final String string) {
        ensureRoot();
        return is(chars(string), ROOT, 0, string.length() - 1);
    }

    public static void add(final String... strings) {
        ensureRoot();
        for (final String string : strings)
            add(chars(string), ROOT, 0, string.length() - 1);
    }

    private static void ensureRoot() {
        if (ROOT == null)
            ROOT = new Dictionary();
    }

    private static char[] chars(final String string) {
        return string.toUpperCase().toCharArray();
    }

    private Dictionary() {
        this.terminus = false;
    }

    private static void add(final char[] word, final Dictionary dictionary, final int index, final int limit) {
        Dictionary branch = getBranch(word, dictionary, index);
        if (index == limit)
            branch.terminus = true;
        else
            add(word, branch, index + 1, limit);
    }

    private static Dictionary getBranch(final char[] word, final Dictionary dictionary, final int index) {
        final char c = word[index];
        dictionary.ensureBranch(c);
        return dictionary.getBranch(c);
    }

    private static boolean is(final char[] word, final Dictionary dictionary, final int index, final int limit) {
        Dictionary branch = dictionary.getBranch(word[index]);
        if (branch == null)
            return false;
        if (index == limit)
            return branch.terminus;
        return is(word, branch, index + 1, limit);
    }
}
 3
Author: Carl Manaster, 2009-06-30 17:31:44

Je sais que ce n'est pas du tout ce que vous demandez, mais n'êtes-vous pas en train de faire cela?

public class Dictionary {
    private static final Set<String> WORDS = new HashSet<String>();

    public static void add(final String... STRINGS) {
        for (String str : STRINGS) {
            WORDS.add(str.toUpperCase());
        }
    }

    public static boolean is(final String STRING) {
        return WORDS.contains(STRING.toUpperCase());
    }
}

Cherchez-vous simplement quelque chose d'un peu plus efficace en mémoire?

 2
Author: Jack Leow, 2009-07-02 15:55:23

L'instruction switch doit utiliser un hachage pour sélectionner la casse à laquelle aller. À partir de là, chaque cas suivant sera également exécuté s'il n'y a pas d'instructions break. Par exemple, avec votre code, si vous activez X, il passera immédiatement à X, puis Y, puis Z. Voir le Tutoriel Java.

 1
Author: David Johnstone, 2009-06-29 23:46:33

Le switch devrait être logarithmique et le if linéaire, en supposant que le compilateur ne trouve rien d'intelligent. Mais les longs switchsont difficiles à lire et aussi sujets aux bogues-comme indiqué, le commutateur que vous avez ci-dessus n'a pas de pauses, et il va tomber dans tous les cas.

Pourquoi ne pas prépeupler un Map à la place, et utiliser simplement Map.get()?

private static final Map<Char, Whatever> BRANCHES = Collections.unmodifiableMap(new HashMap<Char, Whatever>() {{
    put('A', BRANCH.A);
    ...
    put('Z', BRANCH.Z);
}}

public void getBranch(char[] WORD, int INDEX) {
    return BRANCHES.get(WORD[INDEX]);
}

Comme indiqué ci-dessus, si BRANCH est un Enum, ce comportement doit être correctement dans le Enum.

(Ce sont WORD, INDEX et BRANCH ici, de toute façon? D'après les noms, ils devraient être des constantes, mais vous ne pouvez pas vraiment avoir de tableaux constants-le contenu est modifiable; il n'y aurait pas beaucoup d'intérêt à créer une "structure" constante; et il n'y aurait certainement pas beaucoup d'intérêt à changer ou à changer en fonction des constantes....)

 1
Author: David Moles, 2009-06-30 08:52:26

Je pense que c'est plus une question de style que de performance. Je pense que dans ce cas, l'instruction switch est plus appropriée que si. Je ne suis pas sûr qu'il y ait beaucoup de différence dans les performances.

 0
Author: Quincy, 2009-06-30 02:09:40