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

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

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

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

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

что делать?

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

25 ответов


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

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

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

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

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

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

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


острот:

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 установите так, что это приведет к ClassNotFoundException если вы не укажете -- add-модули java.se.ee (спасибо @eckes)
  • недоступно на Android (благодаря Fabian для того, чтобы отметить это), но вы можете просто возьмите исходный код если в вашей системе не хватает javax.xml по какой-то причине. Спасибо к @Bert Regelink для извлечения источника.

класс Hex в commons-codec должен сделать это за вас.

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

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

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

BaseEncoding.base16().decode(string);

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

BaseEncoding.base16().encode(bytes);

на самом деле, я думаю, что BigInteger-это решение очень хорошее:

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

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


на HexBinaryAdapter предоставляет возможность маршалировать и unmarshal между 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;
}

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


острот:

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

для тех из вас, кто заинтересован в фактическом коде за С FractalizeR (мне это было нужно с javax.XML.bind недоступен для Android (по умолчанию)), это происходит от com.солнце.XML.внутренний.связывать.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();
}

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

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]. Но вы, вероятно, уже знали об этом.


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

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

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

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

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

EDIT: как указал @mmyers, этот метод не работает на входе, который содержит подстроки, соответствующие байтам с высоким битовым набором ("80" - "FF"). Объяснение в идентификатор ошибки: 6259307 байт.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;
}

код, представленный Bert Regelink, просто не работает. Попробуйте следующее:

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

для чего это стоит, вот еще одна версия, которая поддерживает строки нечетной длины, не прибегая к конкатенации строк.

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

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

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

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


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

Integer.parseInt(HEXString, 16)

может вызвать проблемы с некоторыми персонажами без преобразование в цифру / целое число

хороший метод работы:

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

Удачи, Удачи


Мне нравится персонаж.цифровое решение, но вот как я его решил

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

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

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...] шестнадцатеричных пар, количество пар...


на основе проголосовавшего за op решения, должно быть немного более эффективно:

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

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


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

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

/**
 * 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"

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

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

        }

    }

}

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

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

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

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

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

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

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

для меня это было решение, 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;
}