Troca de bytes de um inteiro em Java

Sep 14 2020

Tenho esta função minúscula que espera um inte retorna um intcom os bytes trocados:

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);
    }
}

Basicamente, estamos alternando entre endianess. Agora, existe uma maneira melhor / mais eficiente de realizar a tarefa?

Respostas

15 AJNeufeld Sep 14 2020 at 10:47

Nota Histórica

O primeiro comentário sobre a pergunta do OP - muito antes de qualquer resposta ser postada - foi uma pergunta feita por mim mesmo, e foi feita de forma muito objetiva:

Sem usar Integer.reverseBytes (cafeBabe) ?

A questão era dupla:

  1. Indique ao OP que existe um método integrado e é provavelmente a maneira mais ideal de fazer isso.
  2. Pergunte se eles estão tentando reinventar a roda .

Esse comentário / pergunta foi removido. Se o OP respondeu a ela, a resposta também foi excluída e eu nunca a vi.

Essa resposta não está defendendo o OP reinventar a roda . O Java JVM / JIT terá implementado a forma mais eficiente de realizar a operação, usando recursos internos (como @HotSpotIntrinsicCandidate) que não estão necessariamente disponíveis para o usuário final.

Reinventando a roda

public static int byteSwap(int a) {
    return ((a & 0xff000000) >>> 24) |
           ((a & 0x00ff0000) >>> 8)  |
           ((a & 0x0000ff00) << 8)   |
           ((a & 0x000000ff) << 24);
}

Esta função usa 6 constantes distintas, 4 operações AND, 4 turnos e 3 operações OR.

Não há nenhum ponto com a operação AND dentro ((a & 0xff000000) >>> 24)ou dentro, ((a & 0x000000ff) << 24)uma vez que os 24 bits que estão sendo mascarados são imediatamente deslocados para fora. Isso remove 2 constantes e 2 operações AND, o que reduz o código a:

public static int byteSwap(int a) {
    return (a >>> 24)               |
           ((a & 0x00ff0000) >>> 8) |
           ((a & 0x0000ff00) << 8)  |
           (a << 24);
}

Ao retrabalhar a ordem da segunda operação, podemos remover outra constante, o 0x00ff0000, e, em vez disso, reutilizar a 0xff00constante. Isso elimina o carregamento da terceira constante de máscara de bit, salvando instruções adicionais de código de bytes JVM:

public static int byteSwap(int a) {
    return (a >>> 24)           |
           ((a >>> 8) & 0xff00) |
           ((a & 0xff00) << 8)  |
           (a << 24);
}

Esta versão retrabalhada é muito semelhante ao Integer.reverseBytes()código integrado na biblioteca do sistema:

@HotSpotIntrinsicCandidate
public static int reverseBytes(int i) {
    return (i << 24)            |
           ((i & 0xff00) << 8)  |
           ((i >>> 8) & 0xff00) |
           (i >>> 24);
}

Não sei se a diferença no pedido ganha alguma velocidade adicional, mas (dólares para donuts) a @HotSpotIntrinsicCandidateanotação provavelmente sim - então apenas use a função embutida.

6 Bobby Sep 14 2020 at 03:25

Eu gosto, simples de ler.


Pessoalmente, prefiro manter os operadores na nova linha, torna mais fácil ver como as linhas se encaixam:

return ((a & 0xff000000) >>> 24)
     | ((a & 0x00ff0000) >>> 8)
     | ((a & 0x0000ff00) << 8)
     | ((a & 0x000000ff) << 24);

Para seguir sua convenção de formatação.


Você pode colocar o primeiro e o último byte um embaixo do outro, para tornar o padrão ainda mais visível:

return ((a & 0xff000000) >>> 24)
     | ((a & 0x000000ff) << 24)
     | ((a & 0x00ff0000) >>> 8)
     | ((a & 0x0000ff00) << 8);

Isso remove o padrão das máscaras, mas permite deduzir mais facilmente como os bytes estão sendo movidos.


Você também pode converter o intem um bytearray (por exemplo, com a ajuda de a ByteBuffer) e reverter esse array antes de convertê-lo de volta. Mas, no que diz respeito à eficácia, acho que deve ser aquele com menos operações, pois basicamente qualquer operação em um bytearray teria que fazer a mesma coisa e muito mais.


Como já apontado, já existe o Integer.reverseBytes(int)que está disponível desde 1.5.