views:

24309

answers:

10

I am looking for a way to convert a long string (from a dump), that represents hex values into a byte array.

I couldn't have phrased it better than the person that posted the same question here:

http://www.experts-exchange.com/Programming/Programming_Languages/Java/Q_21062554.html

But to keep it original, I'll phrase it my own way: suppose I have a string "00A0BF" that I would like interpreted as the byte[] {0x00,0xA0,0xBf} what should I do?

I am a Java novice and ended up using BigInteger and watching out for leading hex zeros. But I think it is ugly and I am sure I am missing something simple...

+3  A: 

The Hex class in commons-codec should do that for you.

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

skaffman
This also looks good. See org.apache.commons.codec.binary.Hex.decodeHex()
Dave L.
It was interesting. But I found their solution hard to follow. Does it have any advantages over what you proposed (other than checking for even number of chars)?
rafraf
+1  A: 

I've always used a method like

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

this method splits on space delimited hex values but it wouldn't be hard to make it split the string on any other criteria such as into groupings of two characters.

pfranza
The string concatenation is unnecessary. Just use Integer.valueOf(val, 16).
Michael Myers
I've tried using the radix conversions like that before and I've had mixed results
pfranza
You mean it converted incorrectly?
Michael Myers
thanks - oddly it works fine with this string:"9C001C" or "001C21"and fails with this one:"9C001C21"Exception in thread "main" java.lang.NumberFormatException: For input string: "9C001C21" at java.lang.NumberFormatException.forInputString(Unknown Source)
rafraf
A: 

I think will do it for you. I cobbled it together from a similar function that returned the data as a string:

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;
}
Bob King
First, you shouldn't need to convert the string to uppercase. Second, it is possible to append chars directly to a StringBuffer, which should be much more efficient.
Michael Myers
+2  A: 

EDIT: as pointed out by @mmyers, this method doesn't work on input that contains substrings corresponding to bytes with the high bit set ("80" - "FF"). The explanation is at Bug ID: 6259307 Byte.parseByte not working as advertised in the 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;
}
Blair Conrad
Close, but this method fails on the given input "00A0BBF". See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6259307.
Michael Myers
Also strangely it does not deal with "9C"
rafraf
@mmyers: whoa. That's not good. Sorry for th confusion.@ravigad: 9C has the same problem because in this case the high bit is set.
Blair Conrad
+6  A: 

Here is a method that actually works (based on several previous semi-correct answers):

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

The only possible issue that I can see is if the input string is extremely long; calling toCharArray() makes a copy of the string's internal array.

EDIT: Oh, and by the way, bytes are signed in Java, so your input string converts to [0, -96, -65] instead of [0, 160, 191]. But you probably knew that already.

Michael Myers
Thanks - that works perfectly with what I am trying to do...
rafraf
+2  A: 

Actually, I think the BigInteger is solution is very nice:

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

Edit: Not safe for leading zeros, as noted by the poster.

Dave L.
Yep, I remember that. By far the most elegant one!
Torsten Marek
I also thought so initially. And thank you for documenting it - I was just thinking I should... it did some strange things though that I didn't really understand - like omit some leading 0x00 and also mix up the order of 1 byte in a 156 byte string I was playing with.
rafraf
That's a good point about leading 0's. I'm not sure I believe it could mix up the order of bytes, and would be very interested to see it demonstrated.
Dave L.
yeah, as soon as I said it, I didn't believe me either :) I ran a compare of the byte array from BigInteger with mmyers'fromHexString and (with no 0x00) against the offending string - they were identical. The "mix up" did happen, but it may have been something else. I willlook more closely tomorrow
rafraf
+20  A: 

Here's a solution that I think is better than any posted so far:

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

Reasons why it is an improvement:

  • Safe with leading zeros (unlike BigInteger) and with negative byte values (unlike Byte.parseByte)

  • Doesn't convert the String into a char[], or create StringBuilder and String objects for every single byte.

Feel free to add argument checking via assert or exceptions if the argument is not known to be safe.

Dave L.
Just what I needed! :)
Ciaran Archer
Thanks. There should be a built-in for this. Especially that Byte.parseByte croaks on negative values is cumbersome.
Thilo
Thanks, works flawless.
baris_a
+1  A: 

Hi.

The BigInteger() Method from java.math is very Slow and not recommandable.

Integer.parseInt(HEXString, 16)

can cause problems with some characters without converting to Digit / Integer

a Well Working method:

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

Function:

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

Have Fun, Good Luck

Sniper
A: 
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;
}
David V
A: 

I like the Character.digit solution, but here is how I solved it

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