Convertendo entre matrizes de bytes e seqüências hexadecimais em Java
1. Visão geral
Neste tutorial, vamos dar uma olhada em diferentes maneiras de converter uma matriz de bytes em um hexadecimalString,e vice-versa.
Também entenderemos o mecanismo de conversão e escreveremos nossa implementação para conseguir isso.
2. Convertendo entre bytes e hexadecimal
Em primeiro lugar, vamos dar uma olhada na lógica de conversão entre números de bytes e hexadecimais.
2.1. Byte para Hexadecimal
Os bytes são inteiros com sinal de 8 bits em Java. Portanto, precisamosconvert each 4-bit segment to hex separately and concatenate them. Consequentemente, obteremos 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
Vamos implementar esta lógica simples em Java:
public String byteToHex(byte num) {
char[] hexDigits = new char[2];
hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16);
hexDigits[1] = Character.forDigit((num & 0xF), 16);
return new String(hexDigits);
}
Agora, vamos entender o código acima analisando cada operação. Primeiro, criamos uma matriz de caracteres de comprimento 2 para armazenar a saída:
char[] hexDigits = new char[2];
Em seguida, isolamos os bits de ordem superior deslocando à direita 4 bits. E então, aplicamos uma máscara para isolar 4 bits de ordem inferior. O mascaramento é necessário porque os números negativos são internamente representados como complemento de dois do número positivo:
hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16);
Em seguida, convertemos os 4 bits restantes em hexadecimal:
hexDigits[1] = Character.forDigit((num & 0xF), 16);
Finalmente, criamos um objetoString a partir do array char. E, em seguida, retornou esse objeto como matriz hexadecimal convertida.
Agora, vamos entender como isso funcionará para um byte negativo -4:
hexDigits[0]:
1111 1100 >> 4 = 1111 1111 1111 1111 1111 1111 1111 1111
1111 1111 1111 1111 1111 1111 1111 1111 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1111 = 0xf
hexDigits[1]:
1111 1100 & 0xF = 0000 1100 = 0xc
Therefore: -4 (base 10) = 1111 1100 (base 2) = fc (base 16)
Também é importante notar que o métodoCharacter.forDigit _ () _ sempre retorna caracteres minúsculos.
2.2. Hexadecimal para Byte
Agora, vamos converter um dígito hexadecimal em byte. Como sabemos, um byte contém 8 bits. Portanto,we need two hexadecimal digits to create one byte.
Em primeiro lugar, converteremos cada dígito hexadecimal em equivalente binário separadamente.
E então, precisamos concatenar os dois segmentos de quatro bits para obter o equivalente em bytes:
Hexadecimal: 2d
2 = 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 entender isso, uma operação de cada vez.
Primeiro, convertemos caracteres hexadecimais em números inteiros:
int firstDigit = toDigit(hexString.charAt(0));
int secondDigit = toDigit(hexString.charAt(1));
Em seguida, deixamos o dígito mais significativo alterado em 4 bits. Consequentemente, a representação binária possui zeros em quatro bits menos significativos.
Em seguida, adicionamos o dígito menos significativo a ele:
return (byte) ((firstDigit << 4) + secondDigit);
Agora, vamos examinar o métodotoDigit() de perto. Estamos usando o métodoCharacter.digit() para conversão. If the character value passed to this method is not a valid digit in the specified radix, -1 is returned.
Estamos validando o valor de retorno e lançando uma exceção se um valor inválido for passado.
3. Conversão entre matrizes de bytes e hexadecimalStrings
Neste ponto, sabemos como converter um byte em hexadecimal e vice-versa. Vamos dimensionar este algoritmo e converter a matriz de bytes de / para hexadecimalString.
3.1. Matriz de bytes para hexadecimalString
Precisamos percorrer a matriz 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[i]));
}
return hexStringBuffer.toString();
}
Como já sabemos, a saída estará sempre em minúsculas.
3.2. Sequência hexadecimal para matriz de bytes
Em primeiro lugar, precisamos verificar se o comprimento do hexadecimalString é um número par. Isso ocorre porque um hexadecimalString com comprimento ímpar resultará na representação incorreta de bytes.
Agora, vamos iterar pela matriz e converter cada par hexadecimal em um byte:
public byte[] decodeHexString(String hexString) {
if (hexString.length() % 2 == 1) {
throw new IllegalArgumentException(
"Invalid hexadecimal String supplied.");
}
byte[] bytes = new byte[hexString.length() / 2];
for (int i = 0; i < hexString.length(); i += 2) {
bytes[i / 2] = hexToByte(hexString.substring(i, i + 2));
}
return bytes;
}
4. Usando a classeBigInteger
Podemoscreate an object of type BigInteger by passing a signum and byte array.
Agora, podemos gerar o hexadecimalString com a ajuda do formato de método estático definido na classeString:
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 um hexadecimal minúsculo preenchido com zeroString.. Também podemos gerar uma string maiúscula substituindo “x” por “X”.
Alternativamente, poderíamos ter usado o métodotoString() deBigInteger. O sutildifference of using the toString() method is that the output isn’t padded with leading zeros:
public String encodeUsingBigIntegerToString(byte[] bytes) {
BigInteger bigInteger = new BigInteger(1, bytes);
return bigInteger.toString(16);
}
Agora, vamos dar uma olhada na conversão de array hexadecimalString embyte:
public byte[] decodeUsingBigInteger(String hexString) {
byte[] byteArray = new BigInteger(hexString, 16)
.toByteArray();
if (byteArray[0] == 0) {
byte[] output = new byte[byteArray.length - 1];
System.arraycopy(
byteArray, 1, output,
0, output.length);
return output;
}
return byteArray;
}
The toByteArray() method produces an additional sign bit. Escrevemos código específico para lidar com esse bit adicional.
Portanto, devemos estar cientes desses detalhes antes de usar a classeBigInteger para a conversão.
5. Usando a classeDataTypeConverter
A classeDataTypeConverter é fornecida com a biblioteca JAXB. Isso faz parte da biblioteca padrão até o Java 8. A partir do Java 9, precisamos adicionar o módulojava.xml.bind ao tempo de execução explicitamente.
Vamos dar uma olhada na implementação usando a classeDataTypeConverter:
public String encodeUsingDataTypeConverter(byte[] bytes) {
return DatatypeConverter.printHexBinary(bytes);
}
public byte[] decodeUsingDataTypeConverter(String hexString) {
return DatatypeConverter.parseHexBinary(hexString);
}
Conforme mostrado acima, é muito conveniente usar a classeDataTypeConverter. Ooutput of the printHexBinary() method is always in uppercase. Esta classe fornece um conjunto de métodos de impressão e análise para conversão de tipo de dados.
Antes de escolher essa abordagem, precisamos garantir que a classe esteja disponível em tempo de execução.
6. Usando a biblioteca Commons-Codec da Apache
Podemos usar a classeHex 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);
}
Ooutput of encodeHexString is always in lowercase.
7. Usando a biblioteca Guava do Google
Vamos dar uma olhada em como a classeBaseEncoding pode ser usada para codificar e decodificar a matriz de bytes para o hexadecimalString:
public String encodeUsingGuava(byte[] bytes) {
return BaseEncoding.base16().encode(bytes);
}
public byte[] decodeUsingGuava(String hexString) {
return BaseEncoding.base16()
.decode(hexString.toUpperCase());
}
The BaseEncoding encodes and decodes using uppercase characters by default. Se precisarmos usar caracteres minúsculos, uma nova instância de codificação deve ser criada usando o método estáticolowercase.
8. Conclusão
Neste artigo, aprendemos o algoritmo de conversão entre a matriz de bytes em hexadecimalString. Também discutimos vários métodos para codificar matriz de bytes em sequência hexadecimal e vice-versa.
Não é aconselhável adicionar uma biblioteca para usar apenas alguns métodos utilitários. Portanto, se ainda não estivermos usando as bibliotecas externas, devemos usar o algoritmo discutido. A classeDataTypeConverter é outra maneira de codificar / decodificar entre vários tipos de dados.
Finalmente, o código-fonte completo deste tutorial éavailable on GitHub.