Troca de bytes de um inteiro em Java
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
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:
- Indique ao OP que existe um método integrado e é provavelmente a maneira mais ideal de fazer isso.
- 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.
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.