十六进制字符串转float数组

注意:本博客无任何硬广、软广,仅为分享学习知识之意。所有外链中的广告宣传均与本博客无关,请各位看官仔细分辨。若存在侵权文章引用,请及时联系博主“AiyaFocus”,谢谢。

注意:本博客为博主“AiyaFocus”原创,转载请注明出处:https://www.cnblogs.com/AiyaFocus/p/14334164.html,请尊重知识,尊重原创,尊重每个人的劳动成果,谢谢。

 

一、前景提要

有个需求是MySQL数据库中存储了一段十六进制的值,字段类型是longblob,需要将这个值转换并存储到float类型的数组中。假如,该十六进制值共有248个字符(1个"F"就表示一个字符)。

二、注意要点

1. MySQL数据库表字段类型longblob对应的Java类型是byte[]数组;

2. 1个byte=8bit,一个十六进制值,例如"F",只用4bit就可以存储表示,所以,应该是两个十六进制值,如"FF",对应一个byte(字节)。所以,上面说的248个十六进制字符应该对应的是124个字节。注意,这里有个大坑,导致这个问题卡了很久,数据怎么处理都不对,后面会有解释。

3. 解析出来的单个数据为float类型的值,1个float值占4个字节。此处数据解析会有大端模式小端模式的问题,具体怎么解析,要看数据具体是怎么存的。我这边数据是按小端模式存储的。

  这里简单解释一下大端模式和小端模式,此处提供一个百度百科链接:

    a. 小端模式:低地址中存放的是单个数据的低字节,高地址中存放的是单个数据的高字节;例如:假设数据库数据为“0123”,小端模式解析是按照“3210”排列解析。

    b. 大端模式:低地址中存放的是单个数据的高字节,高地址中存放的是单个数据的低字节;例如:假设数据库数据为“0123”,大端模式解析是按照“0123”排列解析。

  什么?没懂!原谅博主水平有限。此处放一个百度百科解释链接:大小端模式 ,请自行食用。若还需要深入了解,请右转自行百度

4. 前4个字节是不需要的,所以不用处理。(此处为当前需求中的解析格式要求,所以下面代码也是按照这个格式解析,此处各位看官还是需要根据自己需求来)。

三、坑是什么?

上面说到有个坑,卡了很久。上面说了,MySQL数据库表字段类型longblob对应的Java类型是byte[]数组,所以类中是用byte[]数组接收结果。本以为此处byte[]数组接收的数据应该是,比如说数据库的数据是十六进制的"FE03"值,那么byte[]数组中,数据存储应该为["00001111", "00001110", "00000000", "00000011"]。然后按照两个十六进制数为一个字节进行计算,比如十六进制的"FE03"值分为"FE"和"03",对应的byte[]数组应该为["11111110", "00000011"]。然而,事实并非如此,因为一开始就错了。怪我自己先入为主的想成数据存储应该像我刚刚讲的那样,然而并不是。实际上类中byte[]数组存储的并不是十六进制(如"F")对应的二进制的值(如"00001111"),而是每个字符对应的二进制的值,如"F"对应ASCII码表中的十进制的值为70,转换成二进制值为"0100 0110",所以类中byte[]数组存储的是数据库该字段值的字符串中,每个字符对应的二进制值(ASCII码表可查。这并不是我们所希望得到的结果。这么大个坑卡了很久才发现,血泪史!(T_T)。

四、新的问题出现

既然,我们已经发现了问题的关键。那么接下来就是转换的数据的问题了,如何将"F"这个字符对应的byte值(也就是"01000110")转换成十六进制对应的二进制的值(也就是我们真正需要的值"00001111"),成了一个新的问题。目前我能想到的就是先将类中byte[]数组中的每一个元素进行转换,转换成对应的字符,然后用“switch……case……”选择结构依次进行比对。如byte[]数组中一个元素值为"01000110",转换成字符则为"F",用“switch……case……”选择结构找到对应的“case”之后,将byte[]数组中该元素值,直接修改为"00001111",其他的以此类推。然后再将两个十六进制对应的二进制值,进行位运算,得到我们真正想要的数据,如十六进制的"F"和"E",分别对应byte二进制值为"00001111"和"00001110"。由于"F"在前,"E"在后,所以两两组合,"FE"对应的byte二进制值应为"11111110",这才是我们真正需要的byte[]数组。然后才是将这个byte[]数组利用位运算处理转换成我们需要的float[]数组。

PS:关于什么是位运算,以及二进制的原码、反码、补码运算。以下给出参考文章链接及B站视频教程链接,或者自行百度。(注意:如果只想解决问题,不想过多过深地去了解这方面的知识,听博主在这啰里吧嗦的,请直接移步下面示例代码区域。:P)

位运算参考资料链接:位运算(&、|、^、~、>>、<<)位运算有什么奇淫技巧?

二进制的原码、反码、补码运算:原码,反码,补码 详解二进制(原码、反码、补码)二进制运算(原码、反码、补码)

B站学习参考视频:进制转换和位运算专题 

 

注意:本博客无任何硬广、软广,仅为分享学习知识之意。所有外链中的广告宣传均与本博客无关,请各位看官仔细分辨。若存在侵权文章引用,请及时联系博主“AiyaFocus”,谢谢。

注意:本博客为博主“AiyaFocus”原创,转载请注明出处:https://www.cnblogs.com/AiyaFocus/p/14334164.html,请尊重知识,尊重原创,尊重每个人的劳动成果,谢谢。

 

五、新的解决办法

不过我并没有使用这种方法,因为在这之前我已经发现了更好的办法(:D)。那就是使用Apache提供的用于摘要运算、编码解码的工具包“commons-codec”,使用该工具包中Hex类的decodeHex静态方法(具体使用参考下面代码)。并且使用这个方法有个惊喜。就是,本来按照我的思路,需要先将字符串中字符对应的byte值转换成十六进制对应的二进制的值,整个byte[]数组都像这样转换之后,再用位运算符,进行两两组合,最终得到我们真正需要的byte[]数组。但这个方法已经帮我们把这两步都搞定了,只用调用decodeHex这个静态方法并传入十六进制的字符串,返回的结果就是我们真正需要的byte[]数组,然后再将此byte[]数组转换成float[]数组即可。该工具包后面我会以附件的形式上传,需要的小伙伴可以自行下载。点击此处下载:commons-codec-1.12.zipPS:因博客园上传文件类型限制,无法上传.jar文件,所以压缩成zip了,下载后解压出来即可使用该jar文件,若有幸被创建博客园的大佬及官方团队看到,建议允许上传的文件类型加入.jar文件类型哈(:P)。

六、示例代码

假设一个blob文件中存储以下一段十六进制值的字符串(共248个十六进制的字符),现需要按照上面的要求解析该字符串,得到float数组。

1 fec9d4493b559c3f957c3b3fae17fa3e1b63d83c28231d3d53ebc33b50e9d23b5c6f983ceb3e283dcbf0d63cd0c1e63ca73adc3afb5d1b3bd74c9a3b4b61553c3f9c7a3d1e7d7a3ced44e13bcdc1043b2d872e3a2cc2803ad951d93bf23c683beefaf63bb1b7703b0a285b3bfdd47f3c95e9ea3b0b60283d0215613b

 

方法一:

 1     public static void main(String[] args) throws Exception {
 2 
 3         // 1.将文件内容读到字节数组中
 4         // a. 通过带缓冲区的文件输入流,加载文件
 5         BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
 6                 "./blob"));
 7         // b. 通过available()获得文件内容的字符个数(文件内容长度)
 8         int len = bis.available();
 9         // c. 根据获得的字符个数,创建相应长度的字节数组,用于存储文件内容
10         byte[] b = new byte[len];
11         // d. 将这个输入流中的内容读取存储到b字节数组中
12         bis.read(b);
13         // e. 判断流是否非空,若不为空则关闭流
14         if (bis != null) {
15             bis.close();
16         }
17 
18         // 2.将传入的存储十六进制字符串的byte数组,转换成真正存储每两个十六进制字符对应的一个二进制值的数组
19         // a. 先将该数组转换成十六进制的字符串
20         String hexString = new String(b);
21         /*
22          * b. 利用Apache提供的用于摘要运算、编码解码的工具包commons-codec,
23          * 将该十六进制的字符串,转换成存储每两个十六进制字符对应的一个二进制值的数组
24          */
25         byte[] data = Hex.decodeHex(hexString);
26         // c. 定义一个float数组变量,指定要返回的float数组的长度
27         // 根据解析规则,前4个字节是不需要的,所以用处理好的byte数组长度减4
28         float[] f = new float[(data.length - 4) / 4];
29         // d. 按照小端模式处理数据,并转换成float数值存入float数组中
30         // 根据解析规则,前4个字节是不需要的,所以下标从4开始
31         for (int i = 4, j = 0; i < data.length; i += 4, j++) {
32             int temp = (data[i] & 0xff);
33             temp = temp | (data[i + 1] & 0xff) << 8;
34             temp = temp | (data[i + 2] & 0xff) << 16;
35             temp = temp | (data[i + 3] & 0xff) << 24;
36             // 将处理后的float值存入float数组中
37             f[j] = Float.intBitsToFloat(temp);
38         }
39 
40         // 3.输出打印该float[]数组的长度及其中的值
41         System.out.println("转换后float[]数组的长度为:" + f.length);
42         System.out.println("转换后float[]数组中的值为:" + Arrays.toString(f));
43     }

 

方法二:

 1     public static void main(String[] args) throws Exception {
 2 
 3         // 1.将文件内容读到字节数组中
 4         // a. 通过带缓冲区的文件输入流,加载文件
 5         BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
 6                 "./blob"));
 7         // b. 根据available()方法获得文件内容的字符个数(文件内容长度),创建相应长度的字节数组,用于存储文件内容
 8         byte[] b = new byte[bis.available()];
 9         // c. 将这个输入流中的内容读取存储到b字节数组中
10         bis.read(b);
11         // d. 判断流是否非空,若不为空则关闭流
12         if (bis != null) {
13             bis.close();
14         }
15 
16         // 2.将传入的存储十六进制字符串的byte数组,转换成真正存储每两个十六进制字符对应的一个二进制值的数组
17         // a. 先将该数组转换成十六进制的字符串
18         String hexString = new String(b);
19         /*
20          * b. 利用Apache提供的用于摘要运算、编码解码的工具包commons-codec,
21          * 将该十六进制的字符串,转换成存储每两个十六进制字符对应的一个二进制值的数组
22          */
23         byte[] data = Hex.decodeHex(hexString);
24         // c. 定义一个float数组变量,指定要返回的float数组的长度
25         // 根据解析规则,前4个字节是不需要的,所以用处理好的byte数组长度减4
26         float[] f = new float[(data.length - 4) / 4];
27         /*
28          * d. 创建字节缓冲区对象。
29          * 利用wrap()方法将指定的byte[]数组包装到缓冲区中,
30          * 并利用order()方法设定此缓冲区的字节顺序,
31          * 要么是BIG_ENDIAN(大端),要么是LITTLE_ENDIAN(小端),
32          * 字节缓冲区的初始顺序始终是BIG_ENDIAN(大端)。
33          */
34         ByteBuffer byteBuffer = ByteBuffer.wrap(data).order(
35                 ByteOrder.LITTLE_ENDIAN);
36         // e. 根据解析规则,前4个字节是不需要的,所以读前4个字节到discardByte数组中,丢弃这前4个字节
37         // 创建discardByte数组,存储需要丢弃的字节值
38         byte[] discardByte = new byte[4];
39         // 获取字节缓冲区中要丢弃的字节,从下标为0开始,读取discardByte该数组长度的字节,并存入discardByte该数组中
40         // 此时字节缓冲区中当前位置已经发生改变
41         byteBuffer.get(discardByte, 0, discardByte.length);
42         // f. 循环float[]数组,从byteBuffer字节缓冲区中继续读取数据,并存入float[]数组中
43         for (int i = 0; i < f.length; i++) {
44             // 调用getFloat()方法,读取字节缓冲区中的float值,并将其存入float[]数组中
45             // getFloat()方法:读取此缓冲区的当前位置之后的4个字节,根据当前的字节顺序将它们组成float值,然后将该位置增加4。 
46             f[i] = byteBuffer.getFloat();
47         }
48 
49         // 3.输出打印该float[]数组的长度及其中的值
50         System.out.println("转换后float[]数组的长度为:" + f.length);
51         System.out.println("转换后float[]数组中的值为:" + Arrays.toString(f));
52     }

 

输出结果:

1 转换后float[]数组的长度为:30
2 转换后float[]数组中的值为:[1.221351, 0.7323697, 0.4884619, 0.026414445, 0.038363606, 0.0059789806, 0.0064365044, 0.018607788, 0.04107563, 0.026237866, 0.028168589, 0.0016802148, 0.002370714, 0.004708867, 0.013023685, 0.061184164, 0.015288619, 0.0068746717, 0.0020257116, 6.657716E-4, 9.823493E-4, 0.0066320715, 0.0035436717, 0.0075372374, 0.0036730582, 0.0033440613, 0.015614745, 0.0071689584, 0.04110722, 0.0034344797]

 

注意:

1. 示例代码中异常并没有针对性的处理,仅为展示整个程序思路及开发代码。正常开发中,需对程序中存在的不同的异常捕获进行不同的处理;

2. 方法一和方法二两段代码仅第2步,解析方式不一样,方法二利用了Java默认提供的ByteBuffer类进行处理,方法一则更接近于底层逻辑实现;

3. 输出结果为MyEclipse中测试的结果,Idea中可能不太一样,结果可能带e的多少次方,会更精确一些,但大体结果一致;

 

注意:本博客无任何硬广、软广,仅为分享学习知识之意。所有外链中的广告宣传均与本博客无关,请各位看官仔细分辨。若存在侵权文章引用,请及时联系博主“AiyaFocus”,谢谢。

注意:本博客为博主“AiyaFocus”原创,转载请注明出处:https://www.cnblogs.com/AiyaFocus/p/14334164.html,请尊重知识,尊重原创,尊重每个人的劳动成果,谢谢。

 

posted @ 2021-01-27 11:43  AiyaFocus  阅读(1249)  评论(0编辑  收藏  举报