运行报错:Exception in thread "main" java.lang.at io.github.pnoker.common.sdk.utils.ParseUtils.decodeHex(ParseUtils.java:98)
Odd number of characters.
at io.github.pnoker.driver.service.impl.DriverCustomsServiceImpl.write(DriverCustomsServiceImpl.java:174)at io.github.pnoker.driver.service.impl.Tess.main(Tess.java:29)Disconnected from the target VM, address: '127.0.0.1:53527', transport: 'socket'
运行界面:
这个报错的核心是 十六进制字符串解码时长度为奇数
触发点在 ParseUtils.decodeHex 方法 —— 十六进制字符串(Hex String)的长度必须是 偶数(1 个字节 = 2 个十六进制字符,如 1A 2B),若传入长度为奇数(如 1 1A3),解码时会直接抛出异常。
一、报错根源拆解
1. 十六进制解码的规则
我查资料ParseUtils.decodeHex 是解析十六进制字符串的工具方法(类似 Apache Commons Codec 的 Hex.decodeHex),要求:
- 输入字符串只能包含 0-9、A-F、a-f 字符(十六进制合法字符);
- 字符串长度必须是 偶数(比如
2A0611A49323长度 12→合法,2A0611A4932长度 11→非法)。
2. 报错调用链分析
Tess.main(29行) → DriverCustomsServiceImpl.write(174行) → ParseUtils.decodeHex(98行)
→ 问题出在:DriverCustomsServiceImpl.write 第 174 行,传入 decodeHex 的字符串是「长度为奇数」或「包含非法字符」的非标准十六进制字符串。
二、分步解决方案(按优先级排序)
步骤 1:定位传入 decodeHex 的异常字符串(关键)
首先要找到「到底是什么字符串」触发了报错,在 DriverCustomsServiceImpl.write 第 174 行添加日志,打印传入的字符串:
// 找到 DriverCustomsServiceImpl.java 第174行,修改前先打印参数
String hexStr = "你当前传入的字符串"; // 替换为实际变量名(比如 param、data 等)
System.out.println("传入 decodeHex 的字符串:" + hexStr);
System.out.println("字符串长度:" + hexStr.length());
System.out.println("字符串是否仅含十六进制字符:" + hexStr.matches("[0-9A-Fa-f]+"));
// 原调用代码(第174行)
byte[] result = ParseUtils.decodeHex(hexStr);
重启应用后,查看控制台输出,重点关注 3 点:
- 字符串长度是否为奇数(比如 1、3、5 等);
- 字符串是否包含非十六进制字符(比如字母 G、中文、空格等);
- 字符串来源(是前端传入、设备推送、配置文件读取?)。
步骤 2:修复异常字符串(按场景处理)
根据步骤 1 打印的结果,分 3 种常见场景修复:
场景 1:字符串长度为奇数(最常见)
比如传入 2A0611A4932(长度 11)、1(长度 1),需补充 1 个字符(默认补 0,或检查来源是否漏传)。
- 修复方案:在调用
decodeHex前,判断长度是否为奇数,若为奇数则在开头 / 结尾补 0(根据业务场景选择,通常补 0):String hexStr = "你传入的字符串"; // 长度为奇数时,开头补0(推荐,符合十六进制编码习惯) if (hexStr.length() % 2 != 0) { hexStr = "0" + hexStr; System.out.println("补0后的字符串:" + hexStr); // 比如 "2A0611A4932" → "02A0611A4932" } // 再解码 byte[] result = ParseUtils.decodeHex(hexStr); - 注意:若业务不允许补 0(比如字符串是设备唯一标识),需检查字符串来源(如前端 / 设备),确认是否漏传了 1 个字符(比如设备本应发送
2A0611A49323,却少发了最后 1 个字符)。
场景 2:字符串包含非十六进制字符
比如传入 2A06G1A49323(含字母 G)、2A 06 11(含空格)、中文123,需过滤非法字符。
- 修复方案:用正则表达式保留仅十六进制字符(0-9、A-F、a-f):
String hexStr = "你传入的字符串"; // 过滤非十六进制字符(只保留 0-9、A-F、a-f) hexStr = hexStr.replaceAll("[^0-9A-Fa-f]", ""); System.out.println("过滤后的字符串:" + hexStr); // 再判断长度是否为奇数(过滤后可能长度变化) if (hexStr.length() % 2 != 0) { hexStr = "0" + hexStr; } // 解码 byte[] result = ParseUtils.decodeHex(hexStr);
场景 3:字符串是 “普通文本” 而非十六进制(误传)
比如传入 device123(普通设备编号)、hello(文本),却被当作十六进制字符串解码,需确认业务逻辑:
- 若业务需要解码十六进制:让字符串来源(前端 / 设备)改为传递标准十六进制字符串(比如
646576696365313233是device123的十六进制编码); - 若业务不需要解码:直接使用原字符串,删除
decodeHex调用(比如只是保存设备编号,无需转字节数组)。
步骤 3:优化代码健壮性(避免后续报错)
在 ParseUtils.decodeHex 调用前添加完整校验,防止非法字符串传入:
/**
* 安全解码十六进制字符串(含校验和容错)
* @param hexStr 待解码字符串
* @return 解码后的字节数组
*/
private byte[] safeDecodeHex(String hexStr) {
if (hexStr == null || hexStr.isEmpty()) {
throw new IllegalArgumentException("十六进制字符串不能为空");
}
// 1. 过滤非十六进制字符
String cleanHex = hexStr.replaceAll("[^0-9A-Fa-f]", "");
// 2. 处理奇数长度(开头补0)
if (cleanHex.length() % 2 != 0) {
cleanHex = "0" + cleanHex;
System.out.println("警告:十六进制字符串长度为奇数,已补0 → " + cleanHex);
}
// 3. 解码(调用原工具方法)
return ParseUtils.decodeHex(cleanHex);
}
// 第174行调用时,改用安全解码方法
byte[] result = safeDecodeHex(hexStr);
三、验证修复效果
-
重启应用后,查看控制台日志:
- 若打印 “补 0 后的字符串”“过滤后的字符串”,说明校验生效;
- 若不再抛出
Odd number of characters异常,且解码后的字节数组符合预期(比如设备编号、数据内容正确),则修复成功。
-
若仍报错:
- 检查
ParseUtils.decodeHex源码,确认是否有其他校验(比如对空字符串、长度为 0 的判断); - 若字符串来源是设备推送,用抓包工具(如 Wireshark、tcpdump)捕获原始数据,确认设备发送的字符串是否符合标准。
- 检查
四、常见问题补充
-
为什么补 0 而不是删最后 1 个字符?
- 十六进制编码中,高位补 0 不改变数据语义(比如
12和012解码后字节数组长度不同,但012是合法的 3 字节数据),而删字符可能导致数据丢失(比如123删为12,可能丢失原数据的最后 4 位)。
- 十六进制编码中,高位补 0 不改变数据语义(比如
-
如何确认字符串是否为标准十六进制?
- 用在线工具验证:将字符串粘贴到 十六进制解码工具,若能正常解码为预期内容(如设备编号、数据),则为标准十六进制。
-
若
ParseUtils.decodeHex本身有 bug?- 替换为成熟工具类(避免自定义工具类的潜在问题),比如 Apache Commons Codec:
<!-- pom.xml 引入依赖(Maven) --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency>// 用 Commons Codec 解码(自动处理部分异常,更稳定) import org.apache.commons.codec.binary.Hex; byte[] result = Hex.decodeHex(cleanHex.toCharArray());
- 替换为成熟工具类(避免自定义工具类的潜在问题),比如 Apache Commons Codec:
总结
这个报错的核心是「十六进制字符串格式非法」,修复流程:
- 打印传入
decodeHex的字符串,确认长度和内容; - 过滤非法字符 + 处理奇数长度(补 0);
- 用安全解码方法封装,避免重复报错。
1万+





