Intercambio de bytes de un entero en Java

Sep 14 2020

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

15 AJNeufeld Sep 14 2020 at 10:47

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:

  1. Indique al OP que existe un método integrado y que probablemente sea la forma más óptima de hacerlo.
  2. 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.

6 Bobby Sep 14 2020 at 03:25

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.