注意:本博客无任何硬广、软广,仅为分享学习知识之意。所有外链中的广告宣传均与本博客无关,请各位看官仔细分辨。若存在侵权文章引用,请及时联系博主“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.zip。PS:因博客园上传文件类型限制,无法上传.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,请尊重知识,尊重原创,尊重每个人的劳动成果,谢谢。