Intercambio de bytes de un entero en Java
Tengo esta pequeña función que espera una inty devuelve una intcon los bytes intercambiados:
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);
}
}
Básicamente, estamos cambiando entre endia. Ahora bien, ¿existe una forma mejor / más eficiente de realizar la tarea?
Respuestas
Nota histórica
El primer comentario sobre la pregunta del OP, mucho antes de que se publicaran las respuestas, fue una pregunta que hice yo mismo, y fue muy intencionalmente:
¿Sin usar Integer.reverseBytes (cafeBabe) ?
La pregunta era doble:
- Indique al OP que existe un método integrado y que probablemente sea la forma más óptima de hacerlo.
- Pregúnteles si están intentando reinventar la rueda .
Ese comentario / pregunta ha sido eliminado. Si el OP respondió, la respuesta también se eliminó y nunca la vi.
Esta respuesta no aboga por el OP reinventar la rueda . Java JVM / JIT habrá implementado la forma más eficiente de realizar la operación, utilizando características internas (tales como @HotSpotIntrinsicCandidate) que no están necesariamente disponibles para el usuario final.
Reinventando la rueda
public static int byteSwap(int a) {
return ((a & 0xff000000) >>> 24) |
((a & 0x00ff0000) >>> 8) |
((a & 0x0000ff00) << 8) |
((a & 0x000000ff) << 24);
}
Esta función utiliza 6 constantes distintas, 4 operaciones AND, 4 turnos y 3 operaciones OR.
No tiene sentido con la operación AND en ((a & 0xff000000) >>> 24)o dentro ((a & 0x000000ff) << 24)ya que los 24 bits que se están enmascarando se desplazan inmediatamente de todos modos. Esto elimina 2 constantes y 2 operaciones AND, lo que reduce el código a:
public static int byteSwap(int a) {
return (a >>> 24) |
((a & 0x00ff0000) >>> 8) |
((a & 0x0000ff00) << 8) |
(a << 24);
}
Al reelaborar el orden de la segunda operación, podemos eliminar otra constante, el 0x00ff0000, y en su lugar reutilizar la 0xff00constante. Esto elimina la carga de la tercera constante de máscara de bits, lo que ahorra instrucciones de código de bytes de JVM adicionales:
public static int byteSwap(int a) {
return (a >>> 24) |
((a >>> 8) & 0xff00) |
((a & 0xff00) << 8) |
(a << 24);
}
Esta versión reelaborada es muy similar al Integer.reverseBytes()código integrado en la biblioteca del sistema:
@HotSpotIntrinsicCandidate
public static int reverseBytes(int i) {
return (i << 24) |
((i & 0xff00) << 8) |
((i >>> 8) & 0xff00) |
(i >>> 24);
}
No sé si la diferencia en el pedido gana velocidad adicional, pero (dólares a donas) @HotSpotIntrinsicCandidateprobablemente lo haga la anotación, así que solo use la función incorporada.
Me gusta, simple de leer.
Personalmente, prefiero mantener a los operadores en la nueva línea, hace que sea más fácil ver cómo las líneas van juntas:
return ((a & 0xff000000) >>> 24)
| ((a & 0x00ff0000) >>> 8)
| ((a & 0x0000ff00) << 8)
| ((a & 0x000000ff) << 24);
Para seguir su convención de formato.
Puede poner el primer y último byte uno debajo del otro, para hacer que el patrón sea aún mejor visible:
return ((a & 0xff000000) >>> 24)
| ((a & 0x000000ff) << 24)
| ((a & 0x00ff0000) >>> 8)
| ((a & 0x0000ff00) << 8);
Eso elimina el patrón de las máscaras, pero permite deducir más fácilmente cómo se mueven los bytes.
También puede convertir el inten una bytematriz (por ejemplo, con la ayuda de a ByteBuffer) e invertir esa matriz antes de volver a convertirla. Pero en lo que respecta a la efectividad, creo que este debería ser el que tenga menos operaciones, ya que básicamente cualquier operación en una bytematriz tendría que hacer lo mismo y más.
Como ya se señaló, ya existe Integer.reverseBytes(int)cuál está disponible desde la 1.5.