Tagged: hex

Hexadecimal Encoding/Decoding with Java


Representing numbers in hexadecimal form, at least in programming, might still be a very difficult problem. There are plenty of googled results for how we do it in any languages.

DatatypeConverter from JAXB

First and foremost way is definitely using one of existing classes and methods in JDK.

Here comes OpenJDK’s implemention of those method.

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

half

number (4bit) character (8bit)
0x0 ( 0) 0x30 ('0')
0x1 ( 1) 0x31 ('1')
0x2 ( 2) 0x32 ('2')
0x3 ( 3) 0x33 ('3')
0x4 ( 4) 0x34 ('4')
0x5 ( 5) 0x35 ('5')
0x6 ( 6) 0x36 ('6')
0x7 ( 7) 0x37 ('7')
0x8 ( 8) 0x38 ('8')
0x9 ( 9) 0x39 ('9')
0xA (10) 0x41 ('A')
0xB (11) 0x42 ('B')
0xC (12) 0x43 ('C')
0xA (13) 0x44 ('D')
0xE (14) 0x45 ('E')
0xF (15) 0x46 ('F')
protected static int encodeHalf(final int decoded) {

    if (decoded < 0x0A) {
        return decoded + 0x30;
    } else {
        return decoded + 0x37;
    }
}


protected static int decodeHalf(final int encoded) {

    if (encoded < 0x41) {
        return encoded - 0x30;
    } else {
        return encoded - 0x37;
    }
}

single

number (8bit) character (16bit)
0x00 (  0) 0x30 0x30 ('00')
.... (  .) .... .... ('..')
0x09 (  9) 0x30 0x39 ('09')
0x0A ( 10) 0x30 0x41 ('0A')
.... ( ..) .... .... ('..')
0x0F ( 15) 0x30 0x46 ('0F')
0x10 ( 16) 0x31 0x30 ('10')
.... ( ..) .... .... ('..')
0x63 ( 99) 0x36 0x33 ('63')
0x64 (100) 0x36 0x34 ('64')
0xC7 (199) 0x43 0x37 ('C7')
0xC8 (200) 0x43 0x38 ('C8')
0xC9 (201) 0x43 0x39 ('C9')
.... (...) .... .... ('..')
0xFF (255) 0x46 0x46 ('FF')
protected static byte[] encodeSingle(final int decoded) {

    final byte[] encoded = new byte[2];

    encoded[0] = (byte) encodeHalf(decoded >> 4);
    encoded[1] = (byte) encodeHalf(decoded & 0x0F);

    return encoded;
}


protected static int decodeSingle(final byte[] encoded) {

    final int high = decodeHalf(encoded[0] & 0xFF);
    final int low = decodeHalf(encoded[1] & 0xFF);

    return (high << 4 | low);
}

multiple

protected static void encodeSingle(final int decoded, final byte[] encoded, final int offset);

protected static int decodeSingle(final byte[] encoded, final int offset);

protected static byte[] encodeMultiple(final byte[] decoded);

protected static byte[] decodeMultiple(final byte[] encoded);

sources

$ svn co http://jinahya.googlecode.com/svn/trunk/com.googlecode.jinahya/hex-codec/ hex-codec

bytes to hex and vice versa


구글에서 “byte hex” 이렇게 두 단어만 검색해보자.

byte[]와 hex로 다시 hex를 byte[]로 변환하는 내용의 블로그 엔트리가 주르륵 흘러 나온다.

검색 결과 중 몇몇 한글 블로그를 보던 중 이상한 부분이 눈에 들어왔다.

일단 Integer.toString(int, int)Integer.parseInt(String, int) 를 사용하는 방법은, 뭐, 틀린 방법은 아니다. (하지만 이 역시 좋은 방법은 아니다. Java 개발자들이 C 개발자들한테 까이는 이유 중에 하나일 수도 있다. 이 짓꺼리 하나 하는데 String 객체를 도데체 몇 개를 만들고 날리는가?)

근데 쌩뚱맞게시리 BigInteger를 사용하면 한 줄로 해결 할 수 있다는 글-귀(but 鬼, not 句)가 보인다.

이걸 어떤 천재(天災)가 처음에 썼는지, 그걸 또 다른 누군가가 아무 생각도 없이 가져다가 뿌려댔는지, 는 잘 모르겠다.

대략 내용은 다음과 같다.


// decoding?
byte[] bytes = new BigInteger(hexText, 16).toByteArray();

// encoding?
String hexText = new BigInteger(bytes).toString(16);

깔끔한 코드에 한표를 던지고 싶네요.. ^^

덕분에 해결했습니다 ^^

다음 단위 테스트를 보자.

public class WrongBigIntegerForHexTest {

    @Test
    public void seemsWork() {
        final byte[] expected = new byte[]{0x09, 0x1A};
        final String encoded = new BigInteger(expected).toString(16);
        Assert.assertEquals(encoded, "91a");
        final byte[] actual = new BigInteger(encoded, 16).toByteArray();
        Assert.assertEquals(actual, expected);
    }

    @Test
    public void seemsWorkHuh() {
        final byte[] expected = new byte[]{0x00, 0x09, 0x1A};
        final String encoded = new BigInteger(expected).toString(16);
        final byte[] actual = new BigInteger(encoded, 16).toByteArray();
        Assert.assertNotEquals(actual, expected);
    }

    @Test(expectedExceptions = NumberFormatException.class)
    public void encodeFail() {
        new BigInteger(new byte[0]).toString(16);
    }

    @Test(expectedExceptions = NumberFormatException.class)
    public void decodeFail() {
        new BigInteger("", 16).toByteArray();
    }
}

뭐, 그렇다는 거다.

여기 매우 버보스 하고 일부 불필요한 코드 까지 겸비한 소스가 있다. 찍어드삼.

HexEncoder.java
HexDecoder.java