Convert a string representation of a hex dump to a byte array using Java?
我正在寻找一种方法来转换一个长字符串(从转储中),它将十六进制值转换为字节数组。
我不可能比那个在这里提出同样问题的人更好地表达它。
但为了保持原样,我会用自己的方式来表达:假设我有一个字符串
1 | byte[] {0x00,0xA0,0xBf} |
我该怎么办?
我是一个Java新手,最终使用了EDCOX1×1,并关注领先的十六进制零。但我觉得它很难看,我肯定我错过了一些简单的东西。
这里有一个我认为比目前任何一个帖子都好的解决方案:
1 2 3 4 5 6 7 8 9 |
改进原因:
使用前导零(与BigInteger不同)和负字节值(与Byte.ParseByte不同)是安全的
不会将字符串转换为
char[] ,也不会为每个字节创建StringBuilder和String对象。没有可能不可用的库依赖项
如果不知道论点是安全的,可以通过
一个衬里:
1 2 3 4 5 6 7 8 9 |
警告:
- 在Java 9拼图中,这不再是(默认)javaSE根的一部分。设置为这样它将导致ClassNotFoundException,除非您指定--添加模块java.se.ee(感谢@
eckes ) - 在Android上不可用(多亏了
Fabian 注意到这点),但是如果您的系统出于某种原因缺少javax.xml ,您可以直接使用源代码。感谢@Bert Regelink 提取源。
通用编解码器中的hex类应该为您做到这一点。
http://commons.apache.org/codec/
1 2 3 4 | import org.apache.commons.codec.binary.Hex; ... byte[] decoded = Hex.decodeHex("00A0BF"); // 0x00 0xA0 0xBF |
现在可以在
1 | BaseEncoding.base16().decode(string); |
要反转它,请使用
1 | BaseEncoding.base16().encode(bytes); |
实际上,我认为biginteger是一个很好的解决方案:
1 |
编辑:如海报所示,前导零不安全。
1 2 3 4 5 6 7 | import javax.xml.bind.annotation.adapters.HexBinaryAdapter; public byte[] hexToBytes(String hexString) { HexBinaryAdapter adapter = new HexBinaryAdapter(); byte[] bytes = adapter.unmarshal(hexString); return bytes; } |
这只是我输入的一个例子…实际上我只是按原样使用它,不需要为使用它做单独的方法。
One-liners:
对于那些对fractalizer的一行代码背后的实际代码感兴趣的人(我需要这样做,因为javax.xml.bind不适用于android(默认情况下)),这来自com.sun.xml.internal.bind.datatypeconverterimpl.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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(); } |
下面是一个实际可行的方法(基于前面的几个半正确答案):
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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()可以复制字符串的内部数组。
编辑:哦,顺便说一下,字节是用Java签名的,所以你的输入字符串转换成[0,-96,-65 ],而不是[0, 160, 191 ]。但你可能已经知道了。
在android中,如果你使用hex,你可以尝试okio。
简单用法:
1 | byte[] bytes = ByteString.decodeHex("c000060000").toByteArray(); |
结果是
1 | [-64, 0, 6, 0, 0] |
编辑:正如@mmyers所指出的,这种方法不适用于包含与高位集("80"-"ff")字节对应的子字符串的输入。解释位于bug id:6259307 byte.parseByte,未按SDK文档中的公告工作。
1 2 3 4 5 6 7 8 9 |
java.math中的
可能导致某些字符出现问题转换为数字/整数
一种修井方法:
1 |
功能:
1 2 3 4 5 6 7 |
玩得开心,祝你好运
BertRegelink提供的代码根本不起作用。请尝试以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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); } } |
值得一提的是,这里的另一个版本支持奇数长度的字符串,而不使用字符串连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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; } |
我总是用这样的方法
1 2 3 4 5 6 7 8 9 10 |
此方法拆分以空格分隔的十六进制值,但不难将字符串拆分为任何其他条件(如两个字符的分组)。
我喜欢character.digit的解决方案,但下面是我如何解决的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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; } |
如果您偏好Java 8流作为编码风格,那么可以使用JDK原语来实现这一点。
1 2 3 4 5 6 7 8 | 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(); |
如果不介意捕获
我发现内核惊慌地想得到对我最有用的解决方案,但是如果十六进制字符串是奇数,就会遇到问题。解决方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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投票解决方案,以下内容应该更有效:
1 2 3 4 5 6 7 8 9 10 11 | 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; } |
因为:到char数组的初始转换将节省charat中的长度检查
到目前为止还不是最干净的解决方案。但它对我很有用,而且格式很好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | private String createHexDump(byte[] msg, String description) { System.out.println(); String result =" " + description; int currentIndex = 0; for(int i=0 ; i<msg.length ; i++){ currentIndex++; if(i == 0){ result += String.format(" %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 == ' ') { characterToAdd = ' '; } result += characterToAdd; } result += String.format(" %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 == ' ') { characterToAdd = ' '; } result += characterToAdd; } } result +=" "; return result; } |
输出:
1 2 3 4 5 | S -> C 0000 0b 00 2e 06 4d 6f 72 69 74 7a 53 6f 6d 65 20 54 | u000B .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 | ? |
参加晚会迟到了,但我把上面戴夫的回答合并成了一个有反动作用的班级,以防万一。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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); } } } |
我的正式解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /** * Decodes a hexadecimally encoded binary string. * <p> * Note that this function does NOT 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 HOX2BIN()函数,但它是Java风格的。
例子:
1 2 |
我知道这是一条很老的线,但我还是想增加我的一分钱。
如果我真的需要编写一个简单的十六进制字符串到二进制的转换器,我想做如下的工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } |
对我来说,这就是解决方案,hex="ff01"然后拆分为ff(255)和01(01)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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; } |
我想会帮你的。我从一个类似的函数中将它拼凑在一起,该函数将数据作为字符串返回:
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } |