Conversão Entre Arrays de Byte e Cordas Hexadecimais em Java
Visão Geral
Neste tutorial, vamos dar uma vista de olhos a diferentes formas de converter uma matriz de bytes para uma corda hexadecimal, e vice-versa.
Tambem vamos compreender o mecanismo de conversão e escrever a nossa implementação para o conseguir.
Conversão entre Byte e Hexadecimal
P>Primeiro de tudo, vamos dar uma vista de olhos à lógica de conversão entre byte e números hexadecimais.
2.1. Byte para Hexadecimal
Os bytes são inteiros de 8 bit assinados em Java. Portanto, precisamos de converter cada segmento de 4 bits para hexadecimal separadamente e concatená-los. Consequentemente, iremos obter dois caracteres hexadecimais após a conversão.
Por exemplo, podemos escrever 45 como 0010 1101 em binário, e o equivalente hexadecimal será “2d”:
0010 = 2 (base 10) = 2 (base 16)1101 = 13 (base 10) = d (base 16)Therefore: 45 = 0010 1101 = 0x2d
P>Deixamos implementar esta lógica simples em Java:
public String byteToHex(byte num) { char hexDigits = new char; hexDigits = Character.forDigit((num >> 4) & 0xF, 16); hexDigits = Character.forDigit((num & 0xF), 16); return new String(hexDigits);}
Agora, vamos compreender o código acima analisando cada operação. Antes de mais, criámos uma matriz de caracteres de comprimento 2 para armazenar a saída:
char hexDigits = new char;
Next, isolámos bits de ordem superior deslocando 4 bits para a direita. E depois, aplicámos uma máscara para isolar os bits de ordem inferior 4 bits. A máscara é necessária porque os números negativos são representados internamente como complemento de dois do número positivo:
hexDigits = Character.forDigit((num >> 4) & 0xF, 16);
Então convertemos os 4 bits restantes para hexadecimal:
hexDigits = Character.forDigit((num & 0xF), 16);
Finalmente, criamos um objecto String a partir da matriz de caracteres. E depois, devolvemos este objecto como um array hexadecimal convertido.
Agora, vamos entender como isto funcionará para um byte negativo -4:
hexDigits:1111 1100 >> 4 = 1111 1111 1111 1111 1111 1111 1111 11111111 1111 1111 1111 1111 1111 1111 1111 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1111 = 0xfhexDigits:1111 1100 & 0xF = 0000 1100 = 0xcTherefore: -4 (base 10) = 1111 1100 (base 2) = fc (base 16)
É também de notar que o método Character.forDigit() devolve sempre caracteres minúsculos.
2.2. Hexadecimal para Byte
Agora, vamos converter um dígito hexadecimal para byte. Como sabemos, um byte contém 8 bits. Portanto, precisamos de dois dígitos hexadecimais para criar um byte.
P>Primeiro de tudo, vamos converter cada dígito hexadecimal em equivalente binário separadamente.
E depois, precisamos de concatenar os dois quatro segmentos de bits para obter o equivalente do byte:
Hexadecimal: 2d2 = 0010 (base 2)d = 1101 (base 2)Therefore: 2d = 0010 1101 (base 2) = 45
Agora, vamos escrever a operação em Java:
public byte hexToByte(String hexString) { int firstDigit = toDigit(hexString.charAt(0)); int secondDigit = toDigit(hexString.charAt(1)); return (byte) ((firstDigit << 4) + secondDigit);}private int toDigit(char hexChar) { int digit = Character.digit(hexChar, 16); if(digit == -1) { throw new IllegalArgumentException( "Invalid Hexadecimal Character: "+ hexChar); } return digit;}
Vamos compreender isto, uma operação de cada vez.
P>Primeiro de tudo, convertemos caracteres hexadecimais em inteiros:
int firstDigit = toDigit(hexString.charAt(0));int secondDigit = toDigit(hexString.charAt(1));
Então deixámos deslocar o dígito mais significativo por 4 bits. Consequentemente, a representação binária tem zeros de pelo menos quatro bits significativos.
Então, adicionámos-lhe o dígito menos significativo:
return (byte) ((firstDigit << 4) + secondDigit);
Agora, vamos examinar de perto o método toDigit(). Estamos a utilizar o método Character.digit() para a conversão. Se o valor de caractere passado para este método não for um dígito válido no radix especificado, -1 é devolvido.
Estamos a validar o valor de retorno e a lançar uma excepção se um valor inválido for passado.
Convertendo entre Arrays de Byte e Cordas Hexadecimais
Neste ponto, sabemos como converter um byte para o hexadecimal, e vice-versa. Vamos escalar este algoritmo e converter uma matriz de bytes para/de Cordas hexadecimais.
3.1. Byte Array para Hexadecimal String
Precisamos de fazer loop através do array e gerar par hexadecimal para cada byte:
public String encodeHexString(byte byteArray) { StringBuffer hexStringBuffer = new StringBuffer(); for (int i = 0; i < byteArray.length; i++) { hexStringBuffer.append(byteToHex(byteArray)); } return hexStringBuffer.toString();}
Como já sabemos, a saída será sempre em minúsculas.
3.2. Hexadecimal String to Byte Array
P>Primeiro de tudo, precisamos de verificar se o comprimento da corda hexadecimal é um número par. Isto porque uma corda hexadecimal com comprimento ímpar resultará numa representação incorrecta de bytes.
Agora, vamos iterar através da matriz e converter cada par hexadecimal num byte:
public byte decodeHexString(String hexString) { if (hexString.length() % 2 == 1) { throw new IllegalArgumentException( "Invalid hexadecimal String supplied."); } byte bytes = new byte; for (int i = 0; i < hexString.length(); i += 2) { bytes = hexToByte(hexString.substring(i, i + 2)); } return bytes;}
Usando a Classe BigInteger
Podemos criar um objecto do tipo BigInteger passando uma matriz de signum e bytes.
Agora, podemos gerar a String hexadecimal com a ajuda do formato do método estático definido na classe String:
public String encodeUsingBigIntegerStringFormat(byte bytes) { BigInteger bigInteger = new BigInteger(1, bytes); return String.format( "%0" + (bytes.length << 1) + "x", bigInteger);}
O formato fornecido irá gerar uma String hexadecimal em letra minúscula e sem quadrícula. Também podemos gerar uma string em maiúsculas substituindo “x” por “X”.
Alternativamente, poderíamos ter usado o método toString() da BigInteger. A diferença subtil de usar o método toString() é que a saída não é acolchoada com zeros:
public String encodeUsingBigIntegerToString(byte bytes) { BigInteger bigInteger = new BigInteger(1, bytes); return bigInteger.toString(16);}
Agora, vamos dar uma olhada à conversão hexadecimal String to byte Array:
public byte decodeUsingBigInteger(String hexString) { byte byteArray = new BigInteger(hexString, 16) .toByteArray(); if (byteArray == 0) { byte output = new byte; System.arraycopy( byteArray, 1, output, 0, output.length); return output; } return byteArray;}
O método toByteArray() produz um bit de sinal adicional. Escrevemos um código específico para lidar com este bit adicional.
Hence, devemos estar atentos a estes detalhes antes de usar a classe BigInteger para a conversão.
Usando a classe DataTypeConverter
A classe DataTypeConverter é fornecida com a biblioteca JAXB. Esta é parte da biblioteca padrão até Java 8. A partir de Java 9, precisamos de adicionar o módulo java.xml.bind ao tempo de execução explicitamente.
p>Vamos dar uma olhada na implementação usando a classe DataTypeConverter:
public String encodeUsingDataTypeConverter(byte bytes) { return DatatypeConverter.printHexBinary(bytes);}public byte decodeUsingDataTypeConverter(String hexString) { return DatatypeConverter.parseHexBinary(hexString);}
Como mostrado acima, é muito conveniente usar a classe DataTypeConverter. A saída do método printHexBinary() está sempre em maiúsculas. Esta classe fornece um conjunto de métodos de impressão e análise para a conversão do tipo de dados.
Antes de escolher esta abordagem, temos de nos certificar de que a classe estará disponível em tempo de execução.
Usando a Biblioteca Apache Commons-Codec
Podemos usar a classe Hex fornecida com a biblioteca Apache commons-codec:
public String encodeUsingApacheCommons(byte bytes) throws DecoderException { return Hex.encodeHexString(bytes);}public byte decodeUsingApacheCommons(String hexString) throws DecoderException { return Hex.decodeHex(hexString);}
A saída do codificadorHexString está sempre em minúsculas.
Usando a Biblioteca da Goiaba do Google
Vejamos como a classe BaseEncoding pode ser usada para codificar e descodificar a matriz de bytes para a String hexadecimal:
public String encodeUsingGuava(byte bytes) { return BaseEncoding.base16().encode(bytes);}public byte decodeUsingGuava(String hexString) { return BaseEncoding.base16() .decode(hexString.toUpperCase());}
A classe BaseEncoding codifica e descodifica usando caracteres maiúsculos por defeito. Se precisarmos de utilizar caracteres minúsculos, deve ser criada uma nova instância de codificação utilizando o método estático minúsculo.
Conclusion
Neste artigo, aprendemos o algoritmo de conversão entre a matriz de bytes para String hexadecimal. Também discutimos vários métodos de codificação de matriz de bytes para string hexadecimal e vice-versa.
Não é aconselhável adicionar uma biblioteca para usar apenas alguns métodos utilitários. Portanto, se não estamos já a utilizar as bibliotecas externas, devemos utilizar o algoritmo discutido. A classe DataTypeConverter é outra forma de codificar/decodificar entre vários tipos de dados.