2014-08-29 18 views
17

Czytam JVM specification o kompilowaniu przełączników i zainteresowałem się sposobem kompilacji instrukcji switch na łańcuchu. Oto metoda badawcza Zbadałem (JDK1.7.0_40):Po co włączać kompilacje String na dwa przełączniki?

static int test(String i) { 
    switch (i) { 
     case "a": return -100; 
     case "45b": return 1; 
     case "c": return 2; 
     default: return -1; 
    } 
} 

Spodziewam ten sposób być zestawiane w prosty lookupswitch na hashcode sznurka, ale nagle

static int test(java.lang.String); 
Code: 
    0: aload_0 
    1: astore_1 
    2: iconst_m1 
    3: istore_2 
    4: aload_1 
    5: invokevirtual #6   // Method java/lang/String.hashCode:()I 
    8: lookupswitch { // 3 
       97: 44 
       99: 72 
      51713: 58 
      default: 83 
     } 
    44: aload_1 
    45: ldc   #7 // String a 
    47: invokevirtual #8 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 
    50: ifeq   83 
    53: iconst_0 
    54: istore_2 
    55: goto   83 
    58: aload_1 
    59: ldc   #9 // String 45b 
    61: invokevirtual #8 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 
    64: ifeq   83 
    67: iconst_1 
    68: istore_2 
    69: goto   83 
    72: aload_1 
    73: ldc   #10 // String c 
    75: invokevirtual #8 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 
    78: ifeq   83 
    81: iconst_2 
    82: istore_2 
    83: iload_2 
    84: tableswitch { // 0 to 2 
       0: 112 
       1: 115 
       2: 117 
      default: 119 
     } 
112: bipush  -100 
114: ireturn 
115: iconst_1 
116: ireturn 
117: iconst_2 
118: ireturn 
119: iconst_m1 
120: ireturn 

Jak można zobaczyć, w gałęziach pierwszego przeszukiwania JVM nie wykonuje zamiast tego rzeczywistych wskaźników generujących pracę dla kolejnego przełącznika tablicowego (linia 84).
Przełącznik powinien działać szybko, więc nie przynosi dużo dodatkowej pracy. Ale w każdym razie, jaki jest cel generowania dodatkowego przełącznika?
Aktualizacja
Rozumiem możliwość kolizji hashCode. Próbuję powiedzieć, że zamiast następnego przełącznika tabelowego, kompilator może przenieść całą rzeczywistą pracę z kolejnego przełącznika tablicowego na pierwszy, a następnie użyć ifeq, aby przeskoczyć na koniec wszystkich gałęzi przełącznika. Tak więc jedna możliwa odpowiedź widzę tutaj: że w pierwszym kompilatorze przełączającym próbuje się wstępnie obliczyć etykietę dla ifeq skakać na podstawie znanej liczby przypadków, ale nie jestem pewien, że jest to jedyny powód.

Update2
Jak @ericbn zasugerował, próbowałem skompilować

switch (i) { 
    case 97: return -100; 
    case 51713: return 1; 
    case 99: return 2; 
    default: return -1; 
} 

z I jako int i kompilator dał mi zwykły lookupswitch.

+0

Wyraźny String (s) może mieć taki sam hashCode ze względu na zasady zaszufladkować. Myślę, że dlatego robi dwa przełączniki. –

+2

nie sądzisz, że istnieje tylko jeden przełącznik? drugi przełącznik służy do warunkowego powrotu. – HuStmpHrrr

+1

@HuStmpHrrr Drugi przełącznik wprowadza pozornie pozbawioną motywacji warstwę pośrednią, skoki podnoszone od stołu mogły zostać włożone bezpośrednio do pierwszego bloku przełączającego jako cele 'goto'. –

Odpowiedz

11

Cite z javac source code:

  * The general approach used is to translate a single 
     * string switch statement into a series of two chained 
     * switch statements: the first a synthesized statement 
     * switching on the argument string's hash value and 
     * computing a string's position in the list of original 
     * case labels, if any, followed by a second switch on the 
     * computed integer value. The second switch has the same 
     * code structure as the original string switch statement 
     * except that the string case labels are replaced with 
     * positional integer constants starting at 0. 
     * 
     * The first switch statement can be thought of as an 
     * inlined map from strings to their position in the case 
     * label list. An alternate implementation would use an 
     * actual Map for this purpose, as done for enum switches. 
     * 
     * With some additional effort, it would be possible to 
     * use a single switch statement on the hash code of the 
     * argument, but care would need to be taken to preserve 
     * the proper control flow in the presence of hash 
     * collisions and other complications, such as 
     * fallthroughs. Switch statements with one or two 
     * alternatives could also be specially translated into 
     * if-then statements to omit the computation of the hash 
     * code.