注意:本博客無任何硬廣、軟廣,僅為分享學習知識之意。所有外鏈中的廣告宣傳均與本博客無關,請各位看官仔細分辨。若存在侵權文章引用,請及時聯系博主“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,請尊重知識,尊重原創,尊重每個人的勞動成果,謝謝。