Преобразование строкового представления шестнадцатеричного дампа в массив байтов с использованием Java?

Я ищу способ преобразования длинной строки (из дампа), которая представляет шестнадцатеричные значения в массив байтов.

Я не мог бы сформулировать это лучше, чем тот, кто задал тот же вопрос здесь.

Но, чтобы сохранить его в оригинале, я буду писать его по-своему: предположим, что у меня есть строка "00A0BF" которую я бы хотел интерпретировать как

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

что мне делать?

Я новичок в Java и в итоге использовал BigInteger и наблюдал за ведущими шестнадцатеричными нулями. Но я думаю, что это уродливо, и я уверен, что мне не хватает чего-то простого.

Ответ 1

Здесь решение, которое, я думаю, лучше, чем любой опубликованный до сих пор:

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;
}

Причины, по которым это улучшение:

  • Безопасно с ведущими нулями (в отличие от BigInteger) и с отрицательными байтовыми значениями (в отличие от Byte.parseByte)

  • Не преобразовывает String в char[] или создает объекты StringBuilder и String для каждого байта.

  • Нет зависимостей библиотек, которые могут быть недоступны

Не стесняйтесь добавлять проверку аргументов через assert или exceptions, если аргумент не известен как безопасный.

Ответ 2

Однострочники:

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);
}

Предупреждения:

  • в Java 9 Jigsaw это уже не часть (по умолчанию) java.se root так что это приведет к исключению ClassNotFoundException, если вы не укажете --add-modules java.se.ee(благодаря @ eckes)
  • Недоступно на Android (спасибо Fabian за то, что заметили это), но вы можете просто взять исходный код, если ваша система не имеет javax.xml по какой-либо причине, Благодаря @Bert Regelink для извлечения источника.

Ответ 4

Теперь вы можете использовать BaseEncoding в guava для достижения этой цели.

BaseEncoding.base16().decode(string);

Чтобы отменить его использование

BaseEncoding.base16().encode(bytes);

Ответ 5

Собственно, я считаю, что решение BigInteger очень хорошее:

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

Изменить: Небезопасно для ведущих нулей, как отмечено на плакате.

Ответ 6

HexBinaryAdapter предоставляет возможность маршалировать и развязывать между String и byte[].

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

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

Это просто пример, который я набрал... Я на самом деле просто использую его как есть и не нужно использовать отдельный метод его использования.

Ответ 7

Однострочники:

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);
}

Для тех из вас, кто интересуется фактическим кодом, стоящим за One-liners от FractalizeR (мне это нужно, поскольку javax.xml.bind недоступен для Android (по умолчанию)), это происходит из 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();
}

Ответ 8

Вот какой метод работает (на основе нескольких предыдущих полуправильных ответов):

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;
}

Единственная возможная проблема, которую я вижу, - это то, что входная строка очень длинная; вызов toCharArray() делает копию внутреннего массива строк.

EDIT: О, и, кстати, байты подписаны на Java, поэтому ваша строка ввода преобразуется в [0, -96, -65] вместо [0, 160, 191]. Но вы, наверное, уже это знали.

Ответ 9

В android, если вы работаете с hex, вы можете попробовать okio.

простое использование:

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

и результат будет

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

Ответ 10

EDIT: как указано в @mmyers, этот метод не работает на входе, который содержит подстроки, соответствующие байтам с высоким набором бит ( "80" - "FF" ). Объяснение приведено в Идентификатор ошибки: 6259307 Byte.parseByte не работает, как описано в документации SDK.

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;
}

Ответ 11

Код, представленный Бертом Реджелинком, просто не работает. Попробуйте следующее:

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);
        }
    }

Ответ 12

Для чего это стоит, здесь другая версия, которая поддерживает строки с нечетной длиной, не прибегая к конкатенации строк.

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;
}

Ответ 13

Я всегда использовал метод типа

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;
}

этот метод разбивается на шестнадцатеричные значения, разделенные пробелами, но было бы непросто заставить его разделить строку на любые другие критерии, например, на группы из двух символов.

Ответ 14

Метод BigInteger() из java.math очень медленный и не рекомендуется.

Integer.parseInt(HEXString, 16)

может вызвать проблемы с некоторыми символами без преобразование в Digit/Integer

a Хороший способ работы:

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

Функции:

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;
}

Удачи, удачи

Ответ 15

Мне нравится решение Character.digit, но вот как я его решил

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;
}

Ответ 16

Я обнаружил, что Kernel Panic имеет самое полезное для меня решение, но столкнулся с проблемами, если шестнадцатеричная строка была нечетным числом. решил это так:

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;
}

Я добавляю число шестнадцатеричных чисел в массив, поэтому я передаю ссылку на массив, который я использую, и int мне нужно преобразовать и вернуть относительное положение следующего шестнадцатеричного числа. Таким образом, у конечного байтового массива [0] количество шестнадцатеричных пар, [1...] шестнадцатеричных пар, то число пар...

Ответ 17

Основываясь на голосовом решении, следующее должно быть немного более эффективным:

  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;
  }

Потому что: начальное преобразование в массив char хранит проверки длины в charAt

Ответ 18

Если вы предпочитаете потоки Java 8 в качестве стиля кодирования, это можно достичь, используя только примитивы 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();

Параметры , 0, s2.size() в функции concatenate коллектора могут быть опущены, если вы не против поймать IOException.

Ответ 19

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;
}

Ответ 20

Мое формальное решение:

/**
 * 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;
}

Как функция PHP hex2bin(), но в стиле Java.

Пример:

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

Ответ 21

Поздно к партии, но я объединил ответ выше DaveL в класс с обратным действием - на всякий случай это помогает.

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;
    }
}

И класс тестирования 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);

        }

    }

}

Ответ 22

Далеко не самое чистое решение. Но он работает для меня и хорошо отформатирован:

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;
}

Выход:

  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                                               | ?

Ответ 23

Думаю, сделаю это за вас. Я объединил его с аналогичной функцией, которая вернула данные в виде строки:

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;
}

Ответ 24

Для меня это было решение, HEX = "FF01", затем разделилось на FF (255) и 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;
}