MQTT报错:Exception in thread main java.lang.at io.github.pnoker.common.sdk.utils.ParseUtils.decodeHex

运行报错: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→非法)。
  1. 报错调用链分析

    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(文本),却被当作十六进制字符串解码,需确认业务逻辑:

  • 若业务需要解码十六进制:让字符串来源(前端 / 设备)改为传递标准十六进制字符串(比如 646576696365313233device123 的十六进制编码);
  • 若业务不需要解码:直接使用原字符串,删除 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);

三、验证修复效果

  1. 重启应用后,查看控制台日志:

    • 若打印 "补 0 后的字符串""过滤后的字符串",说明校验生效;
    • 若不再抛出 Odd number of characters 异常,且解码后的字节数组符合预期(比如设备编号、数据内容正确),则修复成功。
  2. 若仍报错:

    • 检查 ParseUtils.decodeHex 源码,确认是否有其他校验(比如对空字符串、长度为 0 的判断);
    • 若字符串来源是设备推送,用抓包工具(如 Wireshark、tcpdump)捕获原始数据,确认设备发送的字符串是否符合标准。

四、常见问题补充

  1. 为什么补 0 而不是删最后 1 个字符?

    • 十六进制编码中,高位补 0 不改变数据语义(比如 12012 解码后字节数组长度不同,但 012 是合法的 3 字节数据),而删字符可能导致数据丢失(比如 123 删为 12,可能丢失原数据的最后 4 位)。
  2. 如何确认字符串是否为标准十六进制?

    • 用在线工具验证:将字符串粘贴到 十六进制解码工具,若能正常解码为预期内容(如设备编号、数据),则为标准十六进制。
  3. 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());

总结

这个报错的核心是「十六进制字符串格式非法」,修复流程:

  1. 打印传入 decodeHex 的字符串,确认长度和内容;
  2. 过滤非法字符 + 处理奇数长度(补 0);
  3. 用安全解码方法封装,避免重复报错。
相关推荐
啊阿狸不会拉杆7 分钟前
《数字图像处理》第 4 章 - 频域滤波
开发语言·python·数字信号处理·数字图像处理·频率域滤波
他们叫我技术总监18 分钟前
Python 列表、集合、字典核心区别
android·java·python
江沉晚呤时23 分钟前
从零实现 C# 插件系统:轻松扩展应用功能
java·开发语言·microsoft·c#
梁下轻语的秋缘35 分钟前
ESP32-WROOM-32E存储全解析:RAM/Flash/SD卡读写与速度对比
java·后端·spring
wanzhong233338 分钟前
开发日记8-优化接口使其更规范
java·后端·springboot
Knight_AL41 分钟前
Java 多态详解:概念、实现机制与实践应用
java·开发语言
C雨后彩虹1 小时前
volatile 实战应用篇 —— 典型场景
java·多线程·并发·volatile
xie_pin_an1 小时前
从二叉搜索树到哈希表:四种常用数据结构的原理与实现
java·数据结构
Omigeq1 小时前
1.2.1 - 图搜索算法(以A*为例) - Python运动规划库教程(Python Motion Planning)
开发语言·python·机器人·图搜索算法
资深流水灯工程师1 小时前
基于Python的Qt开发之Pyside6 串口接收数据被分割的解决方案
开发语言·python·qt