0 引言
- 与嵌入式软件数据交互过程中,必然涉及各种的、大量的字节操作场景。如:16进制与10进制、2进制间的转换,字符串、byte数组与int之间的转换等。故此有此核心工具类的沉淀。
1 ByteUtils
依赖
<properties><!-- 编程提效工具 --><lombok.version>1.18.22</lombok.version><!-- 日志 --><slf4j.version>1.7.25</slf4j.version><log4j.version>2.20.0</log4j.version><!-- 其他依赖 --><commons.lang3.version>3.9</commons.lang3.version><commons.io.version>2.11.0</commons.io.version><commons.codec.version>1.13</commons.codec.version>
</properties> <dependencies><!-- language enhance / 语言增强 | start --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><scope>provided</scope></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>${commons.lang3.version}</version><scope>provided</scope></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>${commons.io.version}</version><scope>compile</scope></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>${commons.codec.version}</version><scope>provided</scope></dependency><!-- language enhance / 语言增强 | end --><!-- log | start --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version><scope>provided</scope></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>${log4j.version}</version><scope>provided</scope></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>${log4j.version}</version><scope>provided</scope></dependency><!-- this dependency is required for data desensitization --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>${log4j.version}</version><scope>provided</scope></dependency><!-- log | end -->
</dependencies>
ByteUtils
package com.xxxx.utils.bytes;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;/*** 字节操作工具类# @description 与嵌入式软件数据交互过程中,必然涉及各种的、大量的字节操作场景。如:16进制与10进制、2进制间的转换,字符串、byte数组与int之间的转换等,故此有此核心工具类的沉淀。* @version v1.0* @create-time 2022/12/23 18:18* @description ...*/
@Slf4j
public class BytesUtils {private static final Logger logger = LoggerFactory.getLogger(BytesUtils.class);/*** 单个字节的位长* 1byte = 8 bit* 1个16进制位 占 4 bit => 1个字节可代表 2 个 16进制位, 1 个16进制位 = 0.5 个字节**/public final static int BYTE_BIT_LENGTH = 8;/*** 字节转二进制字符串* @note* 单个字节 : 'a' => 97 => 0x61 => 01100001 (二进制) | 兼容 : 无符号数、有符号数* byteToBit( (byte) 0x61 ) = "01100001"* 单个字节 : 250 => 0xFA = 11111010 (二进制) | 无符号数* byteToBit( (byte) 0xFA ) = "11111010"* @param b* @return*/public static String byteToBinaryString(byte b) {return "" +(byte) ((b >> 7) & 0x1) +(byte) ((b >> 6) & 0x1) +(byte) ((b >> 5) & 0x1) +(byte) ((b >> 4) & 0x1) +(byte) ((b >> 3) & 0x1) +(byte) ((b >> 2) & 0x1) +(byte) ((b >> 1) & 0x1) +(byte) ((b >> 0) & 0x1);}/*** 十六进制字符串 转 二进制字符串* @note* hexStringToBinaryString( "61" ) = "01100001" | 'a' => 97 => 0x61 => "01100001"* hexStringToBinaryString( "FA" ) = "11111010"* hexStringToBinaryString( "48656c6c6f" ) = "0100100001100101011011000110110001101111"* 即 "01001000 01100101 01101100 01101100 01101111"* @param hexString* @return*/public static String hexStringToBinaryString(String hexString) {byte[] bytes = com.seres.bigdata.poc.utils.StringUtils.hexStringToBytes(hexString);StringBuilder s = new StringBuilder(64);for (byte aByte : bytes) {s.append(BytesUtils.byteToBinaryString(aByte));}return s.toString();}/**public static String hexStringToBinaryString(String hexString){if(ObjectUtils.isEmpty(hexString) || hexString.length()%2 != 0){return "";}StringBuilder binaryString = new StringBuilder();String tmp;for(int i=0;i<hexString.length(); i++){tmp = "0000" + Integer.toBinaryString( Integer.parseInt(hexString.substring(i, i+1), 16) );binaryString.append( tmp.substring( tmp.length() -4 ) );//String binary = Integer.toBinaryString(Integer.parseInt(hexString.charAt(i) + "", 16));//binaryBuilder.append(String.format("%4s", binary).replace(' ', '0')); // 格式化为4位二进制数}logger.info("hexString : {}, binaryString : {}", hexString, binaryString);return binaryString.toString();}**//*** 字节数组转十六进制表示的字符串* @note* 'H' => 72 => 0x48 ; 'e' => 101 => 0x65* bytesToString(new byte[] { 0x48, 0x65 }) = "4865"* @param byteArray* @return*/public static String bytesToHexString(byte[] byteArray) {StringBuilder stringBuilder = new StringBuilder("");if (byteArray != null) {for (int i = 0; i < byteArray.length; ++i) {stringBuilder.append( byteToHexString(byteArray[i]) );}}return stringBuilder.toString();}/*** 字符串转字节数组* @note* stringToBytes("Hello World", Charset.forName("UTF-8")) => [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]* stringToBytes("娃哈哈", Charset.forName("UTF-8")) => [-27, -88, -125, -27, -109, -120, -27, -109, -120]* @param text* @return*/public static byte [] stringToBytes(String text, Charset charset){if(ObjectUtils.isEmpty(text)){throw new RuntimeException("text is empty! text : " + text);}return charset==null?text.getBytes():text.getBytes(charset);}/*** (多字节的)二进制字符串转二进制数组* @note <p>需了解本方法的局限性</p>* <p>单个字节的二进制字符串 必须满足 java byte(8bit、有符号数) 的取值范围 [-128, 127]</p>* <p>正确示例: binaryStringToBytes("01001000") => out: [72]</p>* <p>错误示例: binaryStringToBytes("11111010") => 0xFA = 250 = 0b1111 0b00001010" => out: [-6] (错误结果,因:超出有符号数byte取值范围)</p>* @param binaryString* @return*/public static byte[] binaryStringToBytes(String binaryString) {if(ObjectUtils.isEmpty(binaryString)){throw new RuntimeException("Fail to convert binary array cause by the empty binary string! binaryString : " + binaryString);}if(binaryString.length() %BYTE_BIT_LENGTH != 0){//不是4的倍数throw new RuntimeException("Fail to convert binary array cause that the binary string is not a multiple of " + BYTE_BIT_LENGTH + "! binaryString : " + binaryString);}// char [] charArray = binaryString.toCharArray() // string 内部由 2个字节的char组成的 char 数组 , 故: 这种转换做法有风险
// byte [] binaryArray = new byte [ binaryString.length() ];
// for (int i = 0; i < charArray.length; i ++) {
// //binaryArray[i] = (byte)charArray[i]; // java char 占用 : 2个字节 ; java byte 占用 1个字节 => 这种做法不正确
// binaryArray[i]
// }int byteSize = binaryString.length()/BYTE_BIT_LENGTH;byte[] binaryArray = new byte[byteSize];for (int i = 0; i < byteSize; i ++) {String byteBinaryStr = binaryString.substring(i*BYTE_BIT_LENGTH, i*BYTE_BIT_LENGTH + BYTE_BIT_LENGTH);//sample "01001000"binaryArray[i] = binaryStringToByte(byteBinaryStr);}return binaryArray;}/*** (多字节的)二进制字符串转 ByteBuffer* @note 解决 java byte 类型仅支持有符号数 的 取值范围 [-128, 127] 的局限性 (ByteBuffer : 支持无符号数)* 在Java中,如果你想要处理无符号的byte数组,你可以使用ByteBuffer类,它可以以大端或小端格式读写字节。* 由于Java中的byte类型是有符号的,范围为-128到127,所以无符号的byte数组需要特别处理。* 例如:* ByteBuffer buffer = ByteBuffer.allocate(5);// 创建一个包含无符号byte的ByteBuffer* buffer.put((byte) 0x80); // 256的二进制补码表示为1000 0000,是一个负数 | 注意,put 方法 传入的是 byte 值,它们会以其补码形式存储。* buffer.put((byte) 0xFF); // 255的二进制补码表示为1111 1111* buffer.flip(); // 翻转Buffer,准备读取* // 读取无符号 byte* int unsignedByte1 = buffer.get() & 0xFF; // 将有符号byte转换为无符号 : 256* int unsignedByte2 = buffer.get() & 0xFF; // 将有符号byte转换为无符号 : 255* 当我们想要读取这些值时,我们使用 get() 方法,并将得到的值与255进行"与"操作(0xFF),这样可以确保我们得到无符号的值。* 这种方法适用于处理单个 byte 值,如果你需要处理更大的无符号数,你可能需要多次调用get()方法,并进行适当的位操作来组成一个无符号整数。* @usage* ByteBuffer byteBuffer = binaryStringToByteBuffer("1111101011111011");//0xFA , 0xFB* byteBuffer.flip();* int value1 = byteBuffer.get(0) & 0xFF;//250* int value2 = byteBuffer.get(1) & 0xFF;//251* @param binaryString* @return*/public static ByteBuffer binaryStringToByteBuffer(String binaryString) {int byteSize = binaryString.length()/BYTE_BIT_LENGTH;if(binaryString.length() % BYTE_BIT_LENGTH != 0){throw new RuntimeException("the binary string must be `" + "`'s times!");}// 创建一个包含 无符号 byte 的 ByteBufferByteBuffer buffer = ByteBuffer.allocate(byteSize);for(int i=0;i<byteSize;i++){String oneByteBinaryString = binaryString.substring(i*BYTE_BIT_LENGTH, i*BYTE_BIT_LENGTH + BYTE_BIT_LENGTH);//sample "01001000"int value = oneByteBinaryStringToInt(oneByteBinaryString);buffer.put( (byte) value );}return buffer;}/*** 单字节的二进制字符串转 Int 整型* @sample* oneByteBinaryStringToInt("1010") => out : 10 => 0xA* oneByteBinaryStringToInt("11111010") => out : 250 => 0xFA* @param oneByteBinaryString* @return*/public static Integer oneByteBinaryStringToInt(String oneByteBinaryString){Integer value = Integer.parseInt(oneByteBinaryString, 2); // 转换为十进制整数return value;}/*** 将位长为8位的二进制字符串转为 1个字节* @note* 注意避坑 : java byte 的局限性* 1. byte 取值范围是 [-128, +127]. Java中的 byte 数据类型是 8 位、有符号的,其取值范围是从 -128 到 127* 2. 基于1,入参(`binaryString`) 的取值范围 : [0b10000000(即-128) , 0b11111111(即-1)] U [0b00000000, 0b01111111]* @param binaryString* sample "01001000" => 72 ; "01111111" => 127* @return*/public static byte binaryStringToByte(String binaryString){int x = 0;// 注 : int 占用 4 个字节for(int j=0; j < BYTE_BIT_LENGTH; j++){String currentBinaryBit = binaryString.substring(BYTE_BIT_LENGTH-1-j, (BYTE_BIT_LENGTH-1-j)+1);x = x + Integer.parseInt( currentBinaryBit ) * ( (int) Math.pow(2, j) );//强制类型转换会直接截断小数部分}return (byte) x;}/*** 二进制字符串转十六进制字符串* @param binaryString* @sample binaryStringToHexString("1111101011111011") => "FAFB" (即: 0xFA, 0xFB)**/public static String binaryStringToHexString(String binaryString){byte[] binaryArray = binaryStringToBytes(binaryString);StringBuilder hexStr = new StringBuilder();int byteSize = binaryArray.length;for(int i=0; i< byteSize; i++){hexStr.append( byteToHexString( binaryArray[i] ) );}return hexStr.toString();}/*** char 转 byte 数组[固定2个字节]* @description 背景 : char类型即可以存储英文字母,也可存储汉字;汉字在java中使用Unicode编码占两个字节* @param c* @return* @sample* charTo2Bytes('G') = [0, 71]* charTo2Bytes('哇') = [84, -57]*/public static byte[] charTo2Bytes(char c) {byte[] b = new byte[2];b[0] = (byte) ((c & 0xFF00) >> 8);b[1] = (byte) (c & 0xFF);return b;}/*** 十六进制字符串转byte数组* @param hexString | sample "48656c6c6f"* @return | sample [72, 101, 108, 108, 111] 即 [0x48, 0x65, 0x6c, 0x6c, 0x6f ] //注 : 以0x开始的数据表示16进制*/public static byte[] hexStringToBytes(String hexString){//String hexString = "48656c6c6f"; // 示例十六进制字符串 "Hello"byte[] byteArray = DatatypeConverter.parseHexBinary(hexString);return byteArray;}/*** 十六进制字符串转字节数组 | 方法2* @note 局限性 : byte 的取值范围 : [-128, 127]* 例如 错误的入参示范 : hexStringToBytes("fffafb") = [-1, -6, -5] (错误结果)* @param hexStr* @return*//**public static byte[] hexStringToBytes(String hexStr) {if (!StringUtils.isEmpty(hexStr) && hexStr.length() % 2 == 0) {byte[] data = null;try {char[] chars = hexStr.toCharArray();data = Hex.decodeHex(chars);} catch (DecoderException var3) {logger.error(var3.getMessage(), var3);}return data;} else {return null;}}**//*** 字节数组 转 十六进制数组字符串* @usage bytesToHexArrayString( new byte [] { 0x34, 0x67} ) = [0x34,0x67]* @param byteArray* @return*/public static String bytesToHexArrayString(byte[] byteArray) {StringBuffer sb = new StringBuffer();sb.append("[");if (byteArray != null) {for (int i = 0; i < byteArray.length; ++i) {sb.append("0x").append(byteToHexString(byteArray[i]));if (i < byteArray.length - 1) {sb.append(",");}}}sb.append("]");return sb.toString();}/*** 字节转16进制字符串* @sample byteToHexString( (byte)0xFA ) = "FA"* @param aByte* @return*/public static String byteToHexString(byte aByte) {char[] digit = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};char[] tempArr = new char[]{digit[aByte >>> 4 & 15], digit[aByte & 15]};String s = new String(tempArr);return s;}/*** INT 转 16进制字符串* @param v* @return* @usage intToHexString(255) => "ff"*/public static String intToHexString(int v) {String hexStr = Integer.toHexString(v);if (hexStr.length() % 2 != 0) {hexStr = "0" + hexStr;}return hexStr;}/*** 16进制转换成为文本字符串* @param hexString* @return* @usage* 1. hexStringToTextString("4861") => "Ha"* 即 : 0x48 , 0x61 => 71 , 97 => 'H' , 'a'*/public static String hexStringToTextString(String hexString) {if (hexString == null || hexString.equals("")) {return null;}hexString = hexString.replace(" ", "");byte[] baKeyword = new byte[hexString.length() / 2];for (int i = 0; i < baKeyword.length; i++) {try {baKeyword[i] = (byte) (0xff & Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16));} catch (Exception e) {logger.error("Fail to convert hex string to text string cause that parse substring to int or byte!hexString:{},index:{},exception:{}", hexString, i, e);//16进制转换成为string类型字符串失败//e.printStackTrace();}}try {hexString = new String(baKeyword, StandardCharsets.UTF_8);} catch (Exception e) {logger.error("Fail to convert hex string to text string cause that byte to string!hexString:{},exception:{}", hexString, e);//16进制转换成为string类型字符串失败}return hexString;}/* ------------------------ 如下方法,待测验 TODO ---------------------- *//*** 从输入流中获取字节数组* @param in* @return*/public static byte[] getBytes(InputStream in) {byte[] data = null;try {byte[] buffer = new byte[1024];int len;for (; (len = in.read(buffer)) > 0; data = addArray(data, buffer, len)) {}} catch (IOException var4) {logger.error(var4.getMessage(), var4);}return data;}public static byte[] addArray(byte[] source, byte[] addedAry) {return addArray(source, addedAry, addedAry.length);}public static byte[] addArray(byte[] source, byte[] addedAry, int availableAddedAryLength) {if (addedAry == null) {return source;} else {byte[] buf = new byte[(source == null ? 0 : source.length) + availableAddedAryLength];int i;if (source != null) {for (i = 0; i < source.length; ++i) {buf[i] = source[i];}}for (i = 0; i < availableAddedAryLength; ++i) {buf[(source == null ? 0 : source.length) + i] = addedAry[i];}return buf;}}public static int contains(byte[] source, byte[] data) {if (source == null || data == null) { return -1; }if (source.length >= data.length && data.length != 0) {int pos = -1;for (int i = 0; i <= source.length - data.length; ++i) {boolean flag = true;for (int j = 0; j < data.length; ++j) {if (source[i + j] != data[j]) { flag = false; }}if (flag) { pos = i; break; }}return pos;}return -1;}public static boolean startWith(byte[] source, byte[] startData) {if (source == null) {return false;} else if (startData == null) {return false;} else if (source.length >= startData.length && startData.length != 0) {boolean flag = true;for (int i = 0; i < startData.length; ++i) {if (source[i] != startData[i]) {flag = false;break;}}return flag;} else {return false;}}public static boolean endWith(byte[] source, byte[] endData) {if (source == null) {return false;} else if (endData == null) {return false;} else if (source.length >= endData.length && endData.length != 0) {boolean flag = true;for (int i = 0; i < endData.length; ++i) {if (source[source.length - endData.length + i] != endData[i]) {flag = false;break;}}return flag;} else {return false;}}public static byte[] removeStart(byte[] source, int len) {if (source == null) {return null;} else if (source.length < len) {return null;} else {byte[] buf = new byte[source.length - len];for (int i = 0; i < buf.length; ++i) {buf[i] = source[i + len];}return buf;}}public static byte[] removeEnd(byte[] source, int len) {if (source == null) {return null;} else if (source.length < len) {return null;} else {byte[] buf = new byte[source.length - len];for (int i = 0; i < buf.length; ++i) {buf[i] = source[i];}return buf;}}public static byte[] subByteArray(byte[] data, int beginIndex, int endIndex) {if (beginIndex < 0) {throw new IndexOutOfBoundsException("beginIndex must bigger than 0,but it is " + beginIndex + "");} else if (endIndex >= data.length) {throw new IndexOutOfBoundsException("endIndex must smaller than data lenght " + data.length + ",but it is " + endIndex + "");} else {byte[] buf = new byte[endIndex - beginIndex + 1];for (int i = beginIndex; i <= endIndex; ++i) {buf[i - beginIndex] = data[i];}return buf;}}public static byte[] remove(byte[] data, int start, int size) {if (data == null) {return null;} else if (start >= data.length) {throw new IndexOutOfBoundsException("start must smaller than data's length:'" + data.length + "',but it is " + start + "");} else if (start + size > data.length) {throw new IndexOutOfBoundsException("start+size must smaller than data's length:'" + data.length + "',but they are " + start + "+" + size + "");} else {byte[] buf = new byte[data.length - size];int i;for (i = 0; i < start; ++i) {buf[i] = data[i];}for (i = start + size; i < data.length; ++i) {buf[i - size] = data[i];}return buf;}}public static byte[] removeContains(byte[] source, byte[] data) {int pos;if (source == null) {return null;} else if (data == null) {return source;} else {if ((pos = contains(source, data)) == -1) {return source;} else if (source.length < data.length) {return null;} else {byte[] buf = new byte[source.length - data.length];int i;for (i = 0; i < pos; ++i) {buf[i] = source[i];}for (i = pos + data.length; i < source.length; ++i) {buf[i - data.length] = source[i];}return buf;}}}/** 打印二进制字符串 (每8bit,打印为1行)* @param bitString 字符串化的二进制字节流* note : 长度必须为8的倍数(因: 1 byte = 8 bit)* sample : "1011011110100011000011110000000000000000000000000000000000000000"* @param isEnable 是否启用本方法的日志打印**/public static String printBitString(String bitString, boolean isEnable){if( bitString == null || bitString.length() %8!=0){//长度必须是8的整数倍throw new RuntimeException("The length of input bit string must be an integer multiple of 8! bit string : " + bitString);}if(bitString == ""){//bitString == ""return "";}StringBuilder result = new StringBuilder();String bit8 = null;while (bitString.length() >= 8) {bit8 = bitString.substring(0, 8);//第 (0, 8] 位 ,共计 8 位result.append( String.format(" %s : %s\n", bit8, BytesUtils.binaryStringToHexString(bit8)) );// 01111111 : 7FbitString = bitString.substring(8); //第 (8 , +∞) 位,递归}return result.toString();}
}