Java实现十六进制编码器
需求
以前也处理过将数字转换为16进制的问题,虽然现在已不记得当时的处理流程了,唯一留下的印象便是,复杂、不满意、可扩展性差、不够灵活……
由于项目需要打算自己实现CSPRNG(Cryptographically Secure Pseudo-Random Number Generator, 伪随机数产生器)。查阅相关资料后发现Java提供了相关的类(SecureRandom类),并且非常好用。1
2
3SecureRandom secureRandom=new SecureRandom();
byte[] bytes=new byte[32];
secureRandom.nextBytes(bytes);
但是生成的随机数结果是一个字节数组,这用起来不是很方便,于是就想着将其转换为16进制字符串。正如大家所知道的那样,Java写的很多工具库都是开源的,查阅相关资料后找到了一种较为简单且有效的实现(开源工具库),该实现共涉及3个类: Strings-String类对应的工具类;HexEncoder-提供16进制编码具体实现,包括编码和解码;Hex通过持有HexEncoder类实例对象提供HexEncoder相关功能(—适配器模式的两种实现—): 对字节数组进行编码,对十六进制字符串进行解码。
编码实现
字节与16进制数之间的对应关系:1
21 byte = 8 bits
4 bits = 一位16进制数
- 16进制编码-字节数组转16进制
根据上述对应关系,可以对字节数组依次进行处理,具体的处理方法: 通过移位运算依次处理该字节的左边四位和右边四位并查找编码映射表取出相应的16进制字符,最后将其放入字节数组输出流。
编码映射表 EncodingTable1
2
3
4protected final byte[] encodingTable = new byte[]{//digital number map to character
(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
};
使用编码映射表可以提高编码速度,从而使进制转的效率得到较大的提升。字节数组转16进制数组的核心实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
* encoding byte element to hexadecimal via shift operation.
*
* @param data byte array
* @param offset the start position
* @param length the length of data to be processed.
* @param os the result stream.
* @return the length of result.
* @throws IOException
*/
public int encode(byte[] data, int offset, int length, OutputStream os) throws IOException {
for (int i = offset; i < offset + length; i++) {
int val = data[i] & 0xff;
// >>>: unsigned right bit shift
os.write(encodingTable[val >>> 4]);
os.write(encodingTable[val & 0x0f]);
}
// Each 4 bits corresponds to a hexadecimal number and one byte is 8 bits,
// so the length of conversion result is 2 times original data length.
return length * 2;
}
- 16进制字符串转字节数组
16进制字符串亦可以是字节数组,字符串中每一个字符占1字节,字节数组中每一个元素也占一个字节,它们在更小度量上是一样的——8 bits,原则上每一位元素均为16进制字符,也就是说每一个元素或字符仅有其右四位为有效位(左边四位默认值为全零)。在将16进制字符串转换为字节数组的时利用位运算可以将相邻的两位16进制数存至一个字节中: 将前一个字符或元素向左移4位后再与另一个字符或元素进行或运算,这样就将其存至1个字节中。16进制字符串转字节数组核心实现: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/**
* decoding hexadecimal string to digital number.
* @param data hexadecimal string.
* @param os the result stream.
* @return the length of result stream.
* @throws IOException
*/
public int decode(String data,OutputStream os)throws IOException{
byte b1,b2;
int length=0;
int offset=0;
int end=data.length();
while(end>offset){
char ch=data.charAt(end-1);
if(!ignore(ch)) break;
end--;
}
int i=offset;
while(i<end){
char ch=data.charAt(i);
while(i<end&&ignore(ch)) {
ch=data.charAt(++i);
}
b1=decodingTable[data.charAt(i++)];
ch=data.charAt(i);
while(i<end&&ignore(ch)){
ch=data.charAt(++i);
}
b2=decodingTable[data.charAt(i++)];
if((b1|b2)<0){
throw new IOException("invalid character encounter in Hex data");
}
os.write((b1<<4)|b2);
length++;
}
return length;
}
需要注意的是该十六进制字符串应该为长度是偶数的字符串,否则在转换过程中将会出现不可处理的错误,由于待转换的字符串由使用者传入,所以在并未进行额外的处理。
Hex工具类
该工具类持有一个类常量对象encoder,并提供类静态方法使用HexEncoder编解码功能,通过该来对HexEncoder类进行了适配,该类主要提供的方法包括: toHexString(byte[] data),decode(String data).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
54
55
56
57
58
59
60/**
* encoding byte array.
* @param data byte array to be encoded.
* @return Hexadecimal String.
*/
public static String toHexString(byte[] data){
return toHexString(data,0,data.length);
}
/**
* encodeing byte array.
* @param data byte array to be encoded.
* @param offset the start of encoding element.
* @param length the length of element to be encoded.
* @return Hexadecimal String.
*/
public static String toHexString(byte[] data,int offset,int length){
byte[] encoded=encode(data,offset,length);
return Strings.fromByteArray(encoded);
}
/**
* encode byte array to be hexadecimal character byte array.
* @param data the byte array to be encoded.
* @param offset the start of encoding position.
* @param length the length of element to be encoded.
* @return hexadecimal character byte array.
*/
public static byte[] encode(byte[] data,int offset,int length){
ByteArrayOutputStream baos=new ByteArrayOutputStream();
try{
encoder.encode(data,offset,length,baos);
}catch(IOException e){
throw new EncoderException("exception encoding Hex string: " + e.getMessage(), e);
}
return baos.toByteArray();
}
/**
* decoding hex string, whitespace will be ignored.
* @param data Hex String.
* @return decoding result.
* @throws IOException
*/
public static byte[] decode(String data)throws IOException{
ByteArrayOutputStream baos=new ByteArrayOutputStream();
decode(data,baos);
return baos.toByteArray();
}
/**
* decoding Hex String.
* @param data Hex String
* @param os OutputStream Object.
* @return the length of decoding producing byte.
* @throws IOException
*/
public static int decode(String data,OutputStream os)throws IOException{
return encoder.decode(data,os);
}Strings类实现
Strings类为String类对应的工具类,该类在此提供了将字节数组转换成字符数组功能和将字节数组转换为字符串功能。
具体实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* converting byte array to string.
*
* @param encoded the byte array to be processed.
* @return
*/
public static String fromByteArray(byte[] encoded) {
return new String(asCharArray(encoded));
}
/**
* converting byte array to char array.
*
* @param encoded the byte array to be processed.
* @return char array.
*/
private static char[] asCharArray(byte[] encoded) {
char[] chars = new char[encoded.length];
for (int i = 0; i < chars.length; i++) {
chars[i] = (char) (encoded[i] & 0xff);
}
return chars;
}测试
主要针对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
35package test;
import util.encoders.Hex;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Arrays;
public class CsprngTest {
public static void main(String[] args)throws IOException {
SecureRandom secureRandom=new SecureRandom();
byte[] bytes=new byte[32];
secureRandom.nextBytes(bytes);
String str= Hex.toHexString(bytes);
System.out.println(str);
byte[] decodeBytes=Hex.decode(str);
boolean flag=Arrays.equals(bytes,decodeBytes);
System.out.println("equals: "+flag);
System.out.println(Hex.toHexString("0123456789abcdef".getBytes())); //测试将字符串转化为16进制
System.out.println(new String(Hex.decode("30313233343536373839616263646566"))); //反向测试将16进制转化为字符串
int integer=1000;
ByteArrayOutputStream baos=new ByteArrayOutputStream();
baos.write(integer);
bytes=baos.toByteArray();
System.out.println("bytes lngth: "+bytes.length);
str=Hex.toHexString(bytes);
System.out.println(str);
}
}