Scambio di byte di un numero intero in Java
Ho questa piccola funzione che si aspetta un inte restituisce un intcon i byte scambiati:
public class Main {
public static void main(String[] args) {
int cafeBabe = 0xcafebabe;
System.out.println(Integer.toHexString(cafeBabe));
System.out.println(Integer.toHexString(byteSwap(cafeBabe)));
System.out.println(Integer.toHexString(byteSwap(byteSwap(cafeBabe))));
}
public static int byteSwap(int a) {
return ((a & 0xff000000) >>> 24) |
((a & 0x00ff0000) >>> 8) |
((a & 0x0000ff00) << 8) |
((a & 0x000000ff) << 24);
}
}
Fondamentalmente, stiamo passando da endianess. Ora, esiste un modo migliore / più efficiente per svolgere il compito?
Risposte
Nota storica
Il primo commento sulla domanda dell'OP - molto prima che le risposte venissero pubblicate - era una domanda posta da me, e chiedeva molto chiaramente:
Senza utilizzare Integer.reverseBytes (cafeBabe) ?
La domanda era duplice:
- Fai notare all'OP che esiste un metodo integrato ed è probabilmente il modo più ottimale per farlo.
- Chiedi se stanno cercando di reinventare la ruota .
Quel commento / domanda è stato rimosso. Se l'OP ha risposto, anche la risposta è stata cancellata e non l'ho mai vista.
Questa risposta non è sostenere l'OP reinventare la ruota . Java JVM / JIT avrà implementato il modo più efficiente di eseguire l'operazione, utilizzando funzionalità interne (come @HotSpotIntrinsicCandidate) che non sono necessariamente disponibili per l'utente finale.
Reinventare la ruota
public static int byteSwap(int a) {
return ((a & 0xff000000) >>> 24) |
((a & 0x00ff0000) >>> 8) |
((a & 0x0000ff00) << 8) |
((a & 0x000000ff) << 24);
}
Questa funzione utilizza 6 costanti distinte, 4 operazioni AND, 4 turni e 3 operazioni OR.
Non ha alcun senso con l'operazione AND in ((a & 0xff000000) >>> 24)o in ((a & 0x000000ff) << 24)poiché i 24 bit che vengono mascherati vengono comunque spostati immediatamente. Questo rimuove 2 costanti e 2 operazioni AND, riducendo il codice a:
public static int byteSwap(int a) {
return (a >>> 24) |
((a & 0x00ff0000) >>> 8) |
((a & 0x0000ff00) << 8) |
(a << 24);
}
Rielaborando l'ordine della seconda operazione, possiamo rimuovere un'altra costante, la 0x00ff0000, e riutilizzare invece la 0xff00costante. Ciò elimina il caricamento della terza costante della maschera di bit, salvando istruzioni aggiuntive del codice byte JVM:
public static int byteSwap(int a) {
return (a >>> 24) |
((a >>> 8) & 0xff00) |
((a & 0xff00) << 8) |
(a << 24);
}
Questa versione rielaborata è molto simile al Integer.reverseBytes()codice integrato nella libreria di sistema:
@HotSpotIntrinsicCandidate
public static int reverseBytes(int i) {
return (i << 24) |
((i & 0xff00) << 8) |
((i >>> 8) & 0xff00) |
(i >>> 24);
}
Non so se la differenza nell'ordinamento guadagni velocità aggiuntiva, ma (dollari in ciambelle) @HotSpotIntrinsicCandidateprobabilmente l' annotazione lo fa, quindi usa la funzione integrata.
Mi piace, semplice da leggere.
Personalmente, preferisco mantenere gli operatori sulla nuova linea, rende più facile vedere come le linee vanno insieme:
return ((a & 0xff000000) >>> 24)
| ((a & 0x00ff0000) >>> 8)
| ((a & 0x0000ff00) << 8)
| ((a & 0x000000ff) << 24);
Per seguire la tua convenzione di formattazione.
Puoi mettere il primo e l'ultimo byte uno sotto l'altro, per rendere il pattern ancora più visibile:
return ((a & 0xff000000) >>> 24)
| ((a & 0x000000ff) << 24)
| ((a & 0x00ff0000) >>> 8)
| ((a & 0x0000ff00) << 8);
Ciò rimuove il pattern dalle maschere, ma consente di dedurre più facilmente come vengono spostati i byte.
Puoi anche convertire il intin un bytearray (ad esempio con l'aiuto di a ByteBuffer) e invertire quell'array prima di riconvertirlo. Ma per quanto riguarda l'efficacia, penso che questo dovrebbe essere quello con il minor numero di operazioni, poiché fondamentalmente qualsiasi operazione su un bytearray dovrebbe fare la stessa cosa e anche di più.
Come già sottolineato, c'è già quello Integer.reverseBytes(int)disponibile dalla 1.5.