Convertendo entre matrizes de bytes e seqüências hexadecimais em Java

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.