使用 Java 将十六进制转储的字符串表示形式转换为字节数组



我正在寻找一种将长字符串(从转储)转换为长字符串的方法,该字符串表示十六进制值为字节数组。

我不能比在这里发布相同问题的人更好地表达它。

但为了保持它的原始性,我将以我自己的方式表达它:假设我有一个字符串"00A0BF"我想将其解释为

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

我该怎么办?

我是一个 Java 新手,最终使用了 BigInteger 并注意了前导十六进制零。但我认为它很丑陋,我确信我错过了一些简单的东西。

更新 (2021) - Java 17 现在包含 java.util.HexFormat(仅用了 25 年):

HexFormat.of().parseHex(s)


对于旧版本的 Java:

这是一个我认为比迄今为止发布的任何解决方案都要好的解决方案:

/* s must be an even-length string. */
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不同)

  • 不会将字符串转换为char[],也不会为每个字节创建 StringBuilder 和 String 对象。

  • 没有可能不可用的库依赖项

如果参数不安全,请随意通过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拼图中,这不再是(默认)java.se 根的一部分设置,以便它将导致类未发现异常,除非您指定--添加模块 java.se.ee(感谢@eckes
  • 在Android上不可用(感谢Fabian注意到这一点),但是如果您的系统由于某种原因缺乏javax.xml,则可以获取源代码。感谢@ Bert Regelink提取源代码。

commons-codec中的十六进制类应该为你做到这一点。

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

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

您现在可以在 guava 中使用 BaseEncoding 来完成此操作。

BaseEncoding.base16().decode(string);

要反转它,请使用

BaseEncoding.base16().encode(bytes);

实际上,我认为BigInteger的解决方案非常好:

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

编辑:如海报所示,前导零不安全

单行:

import jakarta.xml.bind.DatatypeConverter;
public static String toHexString(byte[] array) {
    return DatatypeConverter.printHexBinary(array);
}
public static byte[] toByteArray(String s) {
    return DatatypeConverter.parseHexBinary(s);
}

对于那些对FractalizeR的One-lines背后的实际代码感兴趣的人(我需要它,因为javax.xml.bind默认情况下不适用于Android和Java 9+),它来自jakarta.xml.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();
}

HexBinaryAdapter提供了在Stringbyte[]之间封送和取消封送的能力。

import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
public byte[] hexToBytes(String hexString) {
     HexBinaryAdapter adapter = new HexBinaryAdapter();
     byte[] bytes = adapter.unmarshal(hexString);
     return bytes;
}

这只是我输入的一个例子...我实际上只是按原样使用它,不需要为使用它制定单独的方法。

这是一个实际有效的方法(基于之前的几个半正确答案):

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

我能看到的唯一可能的问题是输入字符串是否非常长;调用CharArray()会复制字符串的内部数组。

编辑:哦,顺便说一下,字节是用Java签名的,所以你的输入字符串转换为[0,-96,-65]而不是[0,160,191]。但你可能已经知道了。

在安卓中,如果你正在使用十六进制,你可以试试okio。

简单用法:

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

结果将是

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

java.math 的 BigInteger() 方法非常慢,不可重新定义。

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

玩得开心,祝你好运

编辑:正如@mmyers所指出的,此方法不适用于包含与高位集("80" - "FF")的字节对应的子字符串的输入。解释位于 Bug ID: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;
}

值得一提的是,这是另一个支持奇数长度字符串的版本,而无需诉诸字符串连接。

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

我喜欢 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;
}

我一直使用这样的方法

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

此方法在空格分隔的十六进制值上拆分,但根据任何其他条件(例如分成两个字符的分组)拆分字符串并不难。

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

我发现 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...] 个十六进制对,然后是对数......

基于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();

如果您不介意捕获IOException,可以省略收集器连接函数中的, 0, s2.size()参数。

如果您的需求不仅仅是偶尔的转换,那么您可以使用HexUtils。

例:

byte[] byteArray = Hex.hexStrToBytes("00A0BF");

这是最简单的情况。您的输入可能包含分隔符(例如 MAC 地址、证书指纹等),您的输入可能是流式传输等。在这种情况下,更容易证明引入像HexUtils这样的外部库是合理的,无论多么小。

在JDK 17中,HexFormat类将满足大多数需求,并且对HexUtils之类的东西的需求大大减少。但是,HexUtils仍然可以用于诸如将非常大量的十六进制(流)或漂亮的打印十六进制(想想电线转储)之类的事情,而JDK HexFormat类无法做到这一点。

(完全披露:我是HexUtils的作者)

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"

派对迟到了,但我已将 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);
        }
    }
}

我知道这是一个非常古老的线程,但仍然喜欢增加我的便士价值。

如果我真的需要编写一个简单的十六进制字符串到二进制转换器,我想按如下方式进行。

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

相关内容

  • 没有找到相关文章

最新更新