How to convert a byte array to a hex string in Java?

后端 未结 27 3454
花落未央
花落未央 2020-11-21 04:19

I have a byte array filled with hex numbers and printing it the easy way is pretty pointless because there are many unprintable elements. What I need is the exact hexcode in

相关标签:
27条回答
  • 2020-11-21 04:52

    Here are some common options ordered from simple (one-liner) to complex (huge library). If you are interested in performance, see the micro benchmarks below.

    Option 1: Code snippet - Simple (only using JDK/Android)

    Option 1a: BigInteger

    One very simple solution is to use the BigInteger's hex representation:

    new BigInteger(1, someByteArray).toString(16);
    

    Note that since this handles numbers not arbitrary byte-strings it will omit leading zeros - this may or may not be what you want (e.g. 000AE3 vs 0AE3 for a 3 byte input). This is also very slow, about 100x slower compared to option 2.

    Option 1b: String.format()

    Using the %X placeholder, String.format() is able to encode most primitive types (short, int, long) to hex:

    String.format("%X", ByteBuffer.wrap(eightByteArray).getLong());
    

    Option 1c: Integer/Long (only 4/8 Byte Arrays)

    If you exclusively have 4 bytes arrays you can use the toHexString method of the Integer class:

    Integer.toHexString(ByteBuffer.wrap(fourByteArray).getInt());
    

    The same works with 8 byte arrays and Long

    Long.toHexString(ByteBuffer.wrap(eightByteArray).getLong());
    

    Option 2: Code snippet - Advanced

    Here is a full featured, copy & pasteable code snippet supporting upper/lowercase and endianness. It is optimized to minimize memory complexity and maximize performance and should be compatible with all modern Java versions (5+).

    private static final char[] LOOKUP_TABLE_LOWER = new char[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66};
    private static final char[] LOOKUP_TABLE_UPPER = new char[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46};
            
    public static String encode(byte[] byteArray, boolean upperCase, ByteOrder byteOrder) {
    
        // our output size will be exactly 2x byte-array length
        final char[] buffer = new char[byteArray.length * 2];
    
        // choose lower or uppercase lookup table
        final char[] lookup = upperCase ? LOOKUP_TABLE_UPPER : LOOKUP_TABLE_LOWER;
    
        int index;
        for (int i = 0; i < byteArray.length; i++) {
            // for little endian we count from last to first
            index = (byteOrder == ByteOrder.BIG_ENDIAN) ? i : byteArray.length - i - 1;
            
            // extract the upper 4 bit and look up char (0-A)
            buffer[i << 1] = lookup[(byteArray[index] >> 4) & 0xF];
            // extract the lower 4 bit and look up char (0-A)
            buffer[(i << 1) + 1] = lookup[(byteArray[index] & 0xF)];
        }
        return new String(buffer);
    }
    
    public static String encode(byte[] byteArray) {
        return encode(byteArray, false, ByteOrder.BIG_ENDIAN);
    }
    

    The full source code with Apache v2 license and decoder can be found here.

    Option 3: Using a small optimized library: bytes-java

    While working on my previous project, I created this little toolkit for working with bytes in Java. It has no external dependencies and is compatible with Java 7+. It includes, among others, a very fast and well tested HEX en/decoder:

    import at.favre.lib.bytes.Bytes;
    ...
    Bytes.wrap(someByteArray).encodeHex()
    

    You can check it out on Github: bytes-java.

    Option 4: Apache Commons Codec

    Of course there is the good 'ol commons codecs. (warning opinion ahead) While working on the project outlined above I analyzed the code and was quite disappointed; a lot of duplicate unorganized code, obsolete and exotic codecs probably only useful for very few and quite over engineered and slow implementations of popular codecs (specifically Base64). I therefore would make an informed decision if you want to use it or an alternative. Anyways, if you still want to use it, here is a code snippet:

    import org.apache.commons.codec.binary.Hex;
    ...
    Hex.encodeHexString(someByteArray));
    

    Option 5: Google Guava

    More often than not you already have Guava as a dependency. If so just use:

    import com.google.common.io.BaseEncoding;
    ...
    BaseEncoding.base16().lowerCase().encode(someByteArray);
    

    Option 6: Spring Security

    If you use the Spring framework with Spring Security you can use the following:

    import org.springframework.security.crypto.codec.Hex
    ...
    new String(Hex.encode(someByteArray));
    

    Option 7: Bouncy Castle

    If you already use the security framework Bouncy Castle you can use its Hex util:

    import org.bouncycastle.util.encoders.Hex;
    ...
    Hex.toHexString(someByteArray);
    

    Not Really Option 8: Java 9+ Compatibility or 'Do Not Use JAXBs javax/xml/bind/DatatypeConverter'

    In previous Java (8 and below) versions the Java code for JAXB was included as runtime dependency. Since Java 9 and Jigsaw modularisation your code cannot access other code outside of it's module without explicit declaration. So be aware if you get an exception like:

    java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
    

    when running on a JVM with Java 9+. If so then switch implementations to any of the alternatives above. See also this question.


    Micro Benchmarks

    Here are results from a simple JMH micro benchmark encoding byte arrays of different sizes. The values are operations per second, so higher is better. Note that micro benchmarks very often do not represent real world behavior, so take these results with a grain of salt.

    | Name (ops/s)         |    16 byte |    32 byte |  128 byte | 0.95 MB |
    |----------------------|-----------:|-----------:|----------:|--------:|
    | Opt1: BigInteger     |  2,088,514 |  1,008,357 |   133,665 |       4 |
    | Opt2/3: Bytes Lib    | 20,423,170 | 16,049,841 | 6,685,522 |     825 |
    | Opt4: Apache Commons | 17,503,857 | 12,382,018 | 4,319,898 |     529 |
    | Opt5: Guava          | 10,177,925 |  6,937,833 | 2,094,658 |     257 |
    | Opt6: Spring         | 18,704,986 | 13,643,374 | 4,904,805 |     601 |
    | Opt7: BC             |  7,501,666 |  3,674,422 | 1,077,236 |     152 |
    | Opt8: JAX-B          | 13,497,736 |  8,312,834 | 2,590,940 |     346 |
    

    Specs: JDK 8u202, i7-7700K, Win10, 24GB Ram. See the full benchmark here.

    0 讨论(0)
  • 2020-11-21 04:54

    Simplest solution, no external libs, no digits constants:

    public static String byteArrayToHex(byte[] a) {
       StringBuilder sb = new StringBuilder(a.length * 2);
       for(byte b: a)
          sb.append(String.format("%02x", b));
       return sb.toString();
    }
    
    0 讨论(0)
  • 2020-11-21 04:54

    Use DataTypeConverter classjavax.xml.bind.DataTypeConverter

    String hexString = DatatypeConverter.printHexBinary(bytes[] raw);

    0 讨论(0)
  • 2020-11-21 04:54

    I would use something like this for fixed length, like hashes:

    md5sum = String.format("%032x", new BigInteger(1, md.digest()));
    
    0 讨论(0)
  • 2020-11-21 04:54

    We don't need to use any external library or to write code based on loops and constants.
    Is enough just this:

    byte[] theValue = .....
    String hexaString = new BigInteger(1, theValue).toString(16);
    
    0 讨论(0)
  • 2020-11-21 04:54

    Ok so there are a bunch of ways to do this, but if you decide to use a library I would suggest poking about in your project to see if something has been implemented in a library that is already part of your project before adding a new library just to do this. For example if you don't already have

    org.apache.commons.codec.binary.Hex

    maybe you do have...

    org.apache.xerces.impl.dv.util.HexBin

    0 讨论(0)
提交回复
热议问题