Convertir une représentation sous forme de chaîne d'un vidage hexadécimal en un tableau d'octets à l'aide de Java?

StackOverflow https://stackoverflow.com/questions/140131

  •  02-07-2019
  •  | 
  •  

Question

Je cherche un moyen de convertir une longue chaîne (issue d'un cliché) représentant les valeurs hexadécimales en un tableau d'octets.

Je n'aurais pas pu mieux l'exprimer que la personne qui avait posté la même question ici .

Mais pour garder l'original, je vais le formuler à ma façon: supposons que j'ai une chaîne "00A0BF" que je voudrais interpréter comme le

byte[] {0x00,0xA0,0xBf}

Que dois-je faire?

Je suis un novice en Java et j'ai fini par utiliser BigInteger et à surveiller les zéros hexagonaux. Mais je pense que c'est moche et je suis sûr qu'il me manque quelque chose de simple.

Était-ce utile?

La solution

Voici une solution qui, à mon avis, est meilleure que celle proposée jusqu'à présent:

public static byte[] hexStringToByteArray(String s) {
    int len = s.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2) {
        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                             + Character.digit(s.charAt(i+1), 16));
    }
    return data;
}

Raisons pour lesquelles c'est une amélioration:

  • Coffre-fort avec les zéros non significatifs (contrairement à BigInteger) et avec les valeurs d'octet négatives (contrairement à Byte.parseByte)

  • Ne convertit pas la chaîne en char [] , ni ne crée d'objets StringBuilder et String pour chaque octet.

  • Aucune dépendance de bibliothèque susceptible de ne pas être disponible

N'hésitez pas à ajouter une vérification d'argument via assert ou des exceptions si l'argument n'est pas réputé sûr.

Autres conseils

Les doublures:

import javax.xml.bind.DatatypeConverter;

public static String toHexString(byte[] array) {
    return DatatypeConverter.printHexBinary(array);
}

public static byte[] toByteArray(String s) {
    return DatatypeConverter.parseHexBinary(s);
}

Avertissements :

  • dans Java 9 Jigsaw, cela ne fait plus partie de la racine (par défaut) java.se défini de sorte qu'il en résulte une exception ClassNotFoundException sauf si vous spécifiez --add-modules java.se.ee (merci à @ eckes )
  • Non disponible sur Android (merci à Fabian de l'avoir noté), mais vous pouvez simplement utiliser le code source. code si javax.xml manque pour votre système. Merci à @ Bert Regelink d’avoir extrait le code source.

La classe Hex dans commons-codec devrait le faire pour vous.

http://commons.apache.org/codec/

import org.apache.commons.codec.binary.Hex;
...
byte[] decoded = Hex.decodeHex("00A0BF");
// 0x00 0xA0 0xBF

Vous pouvez maintenant utiliser BaseEncoding dans guava pour accomplir cela.

BaseEncoding.base16().decode(string);

Pour l'inverser, utilisez

BaseEncoding.base16().encode(bytes);

En fait, je pense que BigInteger est une solution très agréable:

new BigInteger("00A0BF", 16).toByteArray();

Modifier: Ne convient pas aux zéros non significatifs , comme l'indique le posteur.

Le HexBinaryAdapter offre la possibilité de marshal et non marmhal entre String et octet [] .

import javax.xml.bind.annotation.adapters.HexBinaryAdapter;

public byte[] hexToBytes(String hexString) {
     HexBinaryAdapter adapter = new HexBinaryAdapter();
     byte[] bytes = adapter.unmarshal(hexString);
     return bytes;
}

Ce n'est qu'un exemple dans lequel j'ai tapé ... En fait, je l'utilise tel quel et je n'ai pas besoin de créer une méthode distincte pour l'utiliser.

  

Les doublures:

import javax.xml.bind.DatatypeConverter;

public static String toHexString(byte[] array) {
    return DatatypeConverter.printHexBinary(array);
}

public static byte[] toByteArray(String s) {
    return DatatypeConverter.parseHexBinary(s);
}

Pour ceux d'entre vous qui sont intéressés par le code qui se cache derrière les lignes uniques de FractalizeR (J'avais besoin de cela car javax.xml.bind n'est pas disponible pour Android (par défaut)), cela provient de com.sun.xml.internal.bind.DatatypeConverterImpl.java :

public byte[] parseHexBinary(String s) {
    final int len = s.length();

    // "111" is not a valid hex encoding.
    if( len%2 != 0 )
        throw new IllegalArgumentException("hexBinary needs to be even-length: "+s);

    byte[] out = new byte[len/2];

    for( int i=0; i<len; i+=2 ) {
        int h = hexToBin(s.charAt(i  ));
        int l = hexToBin(s.charAt(i+1));
        if( h==-1 || l==-1 )
            throw new IllegalArgumentException("contains illegal character for hexBinary: "+s);

        out[i/2] = (byte)(h*16+l);
    }

    return out;
}

private static int hexToBin( char ch ) {
    if( '0'<=ch && ch<='9' )    return ch-'0';
    if( 'A'<=ch && ch<='F' )    return ch-'A'+10;
    if( 'a'<=ch && ch<='f' )    return ch-'a'+10;
    return -1;
}

private static final char[] hexCode = "0123456789ABCDEF".toCharArray();

public String printHexBinary(byte[] data) {
    StringBuilder r = new StringBuilder(data.length*2);
    for ( byte b : data) {
        r.append(hexCode[(b >> 4) & 0xF]);
        r.append(hexCode[(b & 0xF)]);
    }
    return r.toString();
}

Voici une méthode qui fonctionne réellement (basée sur plusieurs réponses semi-correctes précédentes):

private static byte[] fromHexString(final String encoded) {
    if ((encoded.length() % 2) != 0)
        throw new IllegalArgumentException("Input string must contain an even number of characters");

    final byte result[] = new byte[encoded.length()/2];
    final char enc[] = encoded.toCharArray();
    for (int i = 0; i < enc.length; i += 2) {
        StringBuilder curr = new StringBuilder(2);
        curr.append(enc[i]).append(enc[i + 1]);
        result[i/2] = (byte) Integer.parseInt(curr.toString(), 16);
    }
    return result;
}

Le seul problème possible que je peux voir est si la chaîne d'entrée est extrêmement longue; appeler toCharArray () crée une copie du tableau interne de la chaîne.

EDIT: Oh, et d'ailleurs, les octets sont signés en Java. Votre chaîne d'entrée est donc convertie en [0, -96, -65] au lieu de [0, 160, 191]. Mais vous le saviez probablement déjà.

Sous Android, si vous utilisez hex, vous pouvez essayer okio .

utilisation simple:

byte[] bytes = ByteString.decodeHex("c000060000").toByteArray();

et le résultat sera

[-64, 0, 6, 0, 0]

EDIT: comme l'a souligné @mmyers, cette méthode ne fonctionne pas pour les entrées contenant des sous-chaînes correspondant aux octets dont le bit fort est défini ("80" - "FF"). L'explication est disponible à l'adresse ID de bogue: 6259307 Byte.parseByte ne fonctionnant pas comme prévu dans le SDK. Documentation .

public static final byte[] fromHexString(final String s) {
    byte[] arr = new byte[s.length()/2];
    for ( int start = 0; start < s.length(); start += 2 )
    {
        String thisByte = s.substring(start, start+2);
        arr[start/2] = Byte.parseByte(thisByte, 16);
    }
    return arr;
}

La méthode BigInteger () de java.math est très lente et déconseillée.

Integer.parseInt (HEXString, 16)

peut causer des problèmes avec certains personnages sans conversion en chiffres / entiers

une méthode de travail efficace:

Integer.decode("0xXX") .byteValue()

Fonction:

public static byte[] HexStringToByteArray(String s) {
    byte data[] = new byte[s.length()/2];
    for(int i=0;i < s.length();i+=2) {
        data[i/2] = (Integer.decode("0x"+s.charAt(i)+s.charAt(i+1))).byteValue();
    }
    return data;
}

Amusez-vous, bonne chance

Le code présenté par Bert Regelink ne fonctionne tout simplement pas. Essayez ce qui suit:

import javax.xml.bind.DatatypeConverter;
import java.io.*;

public class Test
{  
    @Test
    public void testObjectStreams( ) throws IOException, ClassNotFoundException
    {     
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);

            String stringTest = "TEST";
            oos.writeObject( stringTest );

            oos.close();
            baos.close();

            byte[] bytes = baos.toByteArray();
            String hexString = DatatypeConverter.printHexBinary( bytes);
            byte[] reconvertedBytes = DatatypeConverter.parseHexBinary(hexString);

            assertArrayEquals( bytes, reconvertedBytes );

            ByteArrayInputStream bais = new ByteArrayInputStream(reconvertedBytes);
            ObjectInputStream ois = new ObjectInputStream(bais);

            String readString = (String) ois.readObject();

            assertEquals( stringTest, readString);
        }
    }

Pour ce que ça vaut, voici une autre version qui supporte les chaînes de longueur impaire, sans recourir à la concaténation de chaînes.

public static byte[] hexStringToByteArray(String input) {
    int len = input.length();

    if (len == 0) {
        return new byte[] {};
    }

    byte[] data;
    int startIdx;
    if (len % 2 != 0) {
        data = new byte[(len / 2) + 1];
        data[0] = (byte) Character.digit(input.charAt(0), 16);
        startIdx = 1;
    } else {
        data = new byte[len / 2];
        startIdx = 0;
    }

    for (int i = startIdx; i < len; i += 2) {
        data[(i + 1) / 2] = (byte) ((Character.digit(input.charAt(i), 16) << 4)
                + Character.digit(input.charAt(i+1), 16));
    }
    return data;
}

J'ai toujours utilisé une méthode comme

public static final byte[] fromHexString(final String s) {
    String[] v = s.split(" ");
    byte[] arr = new byte[v.length];
    int i = 0;
    for(String val: v) {
        arr[i++] =  Integer.decode("0x" + val).byteValue();

    }
    return arr;
}

cette méthode divise les valeurs hexadécimales délimitées par des espaces, mais il ne serait pas difficile de lui faire scinder la chaîne selon d’autres critères, tels que des groupes de deux caractères.

J'aime la solution Character.digit, mais voici comment je l'ai résolue

public byte[] hex2ByteArray( String hexString ) {
    String hexVal = "0123456789ABCDEF";
    byte[] out = new byte[hexString.length() / 2];

    int n = hexString.length();

    for( int i = 0; i < n; i += 2 ) {
        //make a bit representation in an int of the hex value 
        int hn = hexVal.indexOf( hexString.charAt( i ) );
        int ln = hexVal.indexOf( hexString.charAt( i + 1 ) );

        //now just shift the high order nibble and add them together
        out[i/2] = (byte)( ( hn << 4 ) | ln );
    }

    return out;
}

J'ai trouvé la solution la plus utile pour Kernel Panic, mais des problèmes se posaient si la chaîne hexadécimale était un nombre impair. résolu de cette façon:

boolean isOdd(int value)
{
    return (value & 0x01) !=0;
}

private int hexToByte(byte[] out, int value)
{
    String hexVal = "0123456789ABCDEF"; 
    String hexValL = "0123456789abcdef";
    String st = Integer.toHexString(value);
    int len = st.length();
    if (isOdd(len))
        {
        len+=1; // need length to be an even number.
        st = ("0" + st);  // make it an even number of chars
        }
    out[0]=(byte)(len/2);
    for (int i =0;i<len;i+=2)
    {
        int hh = hexVal.indexOf(st.charAt(i));
            if (hh == -1)  hh = hexValL.indexOf(st.charAt(i));
        int lh = hexVal.indexOf(st.charAt(i+1));
            if (lh == -1)  lh = hexValL.indexOf(st.charAt(i+1));
        out[(i/2)+1] = (byte)((hh << 4)|lh);
    }
    return (len/2)+1;
}

J'ajoute un nombre de nombres hexadécimaux à un tableau. Je passe donc la référence au tableau que j'utilise, et le paramètre int dont j'ai besoin est converti et renvoie la position relative du nombre hexadécimal suivant. Donc le tableau d'octets final a [0] nombre de paires hexadécimales, [1 ...] paires hexadécimales, puis le nombre de paires ...

Sur la base de la solution op vote, les éléments suivants devraient être un peu plus efficaces:

  public static byte [] hexStringToByteArray (final String s) {
    if (s == null || (s.length () % 2) == 1)
      throw new IllegalArgumentException ();
    final char [] chars = s.toCharArray ();
    final int len = chars.length;
    final byte [] data = new byte [len / 2];
    for (int i = 0; i < len; i += 2) {
      data[i / 2] = (byte) ((Character.digit (chars[i], 16) << 4) + Character.digit (chars[i + 1], 16));
    }
    return data;
  }

Parce que: la conversion initiale en tableau de caractères épargne les vérifications de longueur dans charAt

Si vous avez une préférence pour les flux Java 8 en tant que style de codage, vous pouvez le faire en utilisant uniquement les primitives JDK.

String hex = "0001027f80fdfeff";

byte[] converted = IntStream.range(0, hex.length() / 2)
    .map(i -> Character.digit(hex.charAt(i * 2), 16) << 4 | Character.digit(hex.charAt((i * 2) + 1), 16))
    .collect(ByteArrayOutputStream::new,
             ByteArrayOutputStream::write,
             (s1, s2) -> s1.write(s2.toByteArray(), 0, s2.size()))
    .toByteArray();

Les paramètres , 0, s2.size () de la fonction de concaténation du collecteur peuvent être omis si cela ne vous dérange pas d'attraper IOException .

public static byte[] hex2ba(String sHex) throws Hex2baException {
    if (1==sHex.length()%2) {
        throw(new Hex2baException("Hex string need even number of chars"));
    }

    byte[] ba = new byte[sHex.length()/2];
    for (int i=0;i<sHex.length()/2;i++) {
        ba[i] = (Integer.decode(
                "0x"+sHex.substring(i*2, (i+1)*2))).byteValue();
    }
    return ba;
}

Ma solution formelle:

/**
 * Decodes a hexadecimally encoded binary string.
 * <p>
 * Note that this function does <em>NOT</em> convert a hexadecimal number to a
 * binary number.
 *
 * @param hex Hexadecimal representation of data.
 * @return The byte[] representation of the given data.
 * @throws NumberFormatException If the hexadecimal input string is of odd
 * length or invalid hexadecimal string.
 */
public static byte[] hex2bin(String hex) throws NumberFormatException {
    if (hex.length() % 2 > 0) {
        throw new NumberFormatException("Hexadecimal input string must have an even length.");
    }
    byte[] r = new byte[hex.length() / 2];
    for (int i = hex.length(); i > 0;) {
        r[i / 2 - 1] = (byte) (digit(hex.charAt(--i)) | (digit(hex.charAt(--i)) << 4));
    }
    return r;
}

private static int digit(char ch) {
    int r = Character.digit(ch, 16);
    if (r < 0) {
        throw new NumberFormatException("Invalid hexadecimal string: " + ch);
    }
    return r;
}

Est semblable à la Fonction PHP hex2bin () mais dans le style Java.

Exemple:

String data = new String(hex2bin("6578616d706c65206865782064617461"));
// data value: "example hex data"

De loin pas la solution la plus propre. Mais cela fonctionne pour moi et est bien formaté:

private String createHexDump(byte[] msg, String description) {
    System.out.println();
    String result = "\n" + description;
    int currentIndex = 0;
    for(int i=0 ; i<msg.length ; i++){
        currentIndex++;
        if(i == 0){
            result += String.format("\n  %04x ", i);
        }
        if(i % 16 == 0 && i != 0){
            result += " | ";
            for(int j=(i-16) ; j<msg.length && j<i ; j++) {
                char characterToAdd = (char) msg[j];
                if (characterToAdd == '\n') {
                    characterToAdd = ' ';
                }
                result += characterToAdd;
            }

            result += String.format("\n  %04x ", i);
        }

        result += String.format("%02x ", msg[i]);
    }

    if(currentIndex % 16 != 0){
        int fitIns = msg.length / 16;
        int leftOvers = msg.length - (fitIns * 16);
        for(int i=0 ; i<16-leftOvers ; i++){
            result += "   ";
        }

        result += " | ";

        for(int i=msg.length-leftOvers ; i<msg.length ; i++){
            char characterToAdd = (char) msg[i];
            if (characterToAdd == '\n') {
                characterToAdd = ' ';
            }
            result += characterToAdd;
        }
    }

    result += "\n";

    return result;
}

La sortie:

  S -> C
    0000 0b 00 2e 06 4d 6f 72 69 74 7a 53 6f 6d 65 20 54  |  .Heyyy Some T
    0010 43 50 20 73 74 75 66 66 20 49 20 63 61 70 74 75  | CP stuff I captu
    0020 72 65 64 2e 2e 77 65 6c 6c 20 66 6f 72 6d 61 74  | red..well format
    0030 3f                                               | ?

En retard pour le parti, mais j'ai fusionné la réponse ci-dessus de DaveL dans une classe avec l'action inverse - juste au cas où cela aiderait.

public final class HexString {
    private static final char[] digits = "0123456789ABCDEF".toCharArray();

    private HexString() {}

    public static final String fromBytes(final byte[] bytes) {
        final StringBuilder buf = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            buf.append(HexString.digits[(bytes[i] >> 4) & 0x0f]);
            buf.append(HexString.digits[bytes[i] & 0x0f]);
        }
        return buf.toString();
    }

    public static final byte[] toByteArray(final String hexString) {
        if ((hexString.length() % 2) != 0) {
            throw new IllegalArgumentException("Input string must contain an even number of characters");
        }
        final int len = hexString.length();
        final byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                    + Character.digit(hexString.charAt(i + 1), 16));
        }
        return data;
    }
}

Et la classe de test JUnit:

public class TestHexString {

    @Test
    public void test() {
        String[] tests = {"0FA1056D73", "", "00", "0123456789ABCDEF", "FFFFFFFF"};

        for (int i = 0; i < tests.length; i++) {
            String in = tests[i];
            byte[] bytes = HexString.toByteArray(in);
            String out = HexString.fromBytes(bytes);
            System.out.println(in); //DEBUG
            System.out.println(out); //DEBUG
            Assert.assertEquals(in, out);

        }

    }

}

Je sais que c’est un très vieux sujet, mais j’aimerais quand même ajouter mon penny.

Si j’ai vraiment besoin de coder un convertisseur hexadécimal simple en un convertisseur binaire, j’aimerais le faire comme suit.

public static byte[] hexToBinary(String s){

  /*
   * skipped any input validation code
   */

  byte[] data = new byte[s.length()/2];

  for( int i=0, j=0; 
       i<s.length() && j<data.length; 
       i+=2, j++)
  {
     data[j] = (byte)Integer.parseInt(s.substring(i, i+2), 16);
  }

  return data;
}

Je pense que le fera pour vous. Je l'ai bricolé à partir d'une fonction similaire qui renvoyait les données sous forme de chaîne:

private static byte[] decode(String encoded) {
    byte result[] = new byte[encoded/2];
    char enc[] = encoded.toUpperCase().toCharArray();
    StringBuffer curr;
    for (int i = 0; i < enc.length; i += 2) {
        curr = new StringBuffer("");
        curr.append(String.valueOf(enc[i]));
        curr.append(String.valueOf(enc[i + 1]));
        result[i] = (byte) Integer.parseInt(curr.toString(), 16);
    }
    return result;
}

Pour moi, c'était la solution, HEX = "FF01" puis divisé en FF (255) et 01 (01)

private static byte[] BytesEncode(String encoded) {
    //System.out.println(encoded.length());
    byte result[] = new byte[encoded.length() / 2];
    char enc[] = encoded.toUpperCase().toCharArray();
    String curr = "";
    for (int i = 0; i < encoded.length(); i=i+2) {
        curr = encoded.substring(i,i+2);
        System.out.println(curr);
        if(i==0){
            result[i]=((byte) Integer.parseInt(curr, 16));
        }else{
            result[i/2]=((byte) Integer.parseInt(curr, 16));
        }

    }
    return result;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top