• 十六进制字符串转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,请尊重知识,尊重原创,尊重每个人的劳动成果,谢谢。

  • 相关阅读:
    Java反射机制
    前端学PHP之基础语法
    详细了解HTML标签内容模型
    HTML的音频和视频
    三个不常用的HTML元素:<details>、<summary>、<dialog>
    HTML内联元素
    HTML5结构元素
    sublime简要笔记
    ISO语言代码
    使用余弦定理制作磁盘形状h5音乐播放器
  • 原文地址:https://www.cnblogs.com/AiyaFocus/p/14334164.html
Copyright © 2020-2023  润新知