深入讲解JavaScript中将字符串转换为十六进制数组的完整方案,涵盖基于TextEncoder的UTF-8字节流转换与基于charCodeAt的UTF-16码点转换,提供补零格式化、带前缀输出及逆向还原的完整实现。
js字符串怎么转十六进制数组
时间:2026-11-13
浏览:466
文章目录
byte.toString(16).padStart(2, '0') 转化为固定两位的十六进制文本。如果是处理网络通讯、底层二进制协议或包含汉字、Emoji 的场景,现代标准推荐使用 TextEncoder 输出 UTF-8 字节;如果是仅针对纯 ASCII 字符或需要直接提取 UTF-16 内存码点,则可以使用 charCodeAt() 遍历。
一、为什么必须明确“字符编码”?
在写具体代码前,必须厘清一个经常导致 Bug 的关键概念:十六进制不是一种独立的数据类型,而是数字在以 16 为基数时的表示形式。一个字符串变成十六进制数组,本质上是把文本拆解成一串数字,再将每个数字格式化为 16 进制字符串。
计算机存储文本依赖编码规范。在 JavaScript 中,开发者通常会面对两种“十六进制数组”需求:
- 场景 A:UTF-8 字节十六进制数组(绝大多数实际工程所需):例如与后端通信、硬件蓝牙通讯、加解密(AES/MD5)或下载二进制文件。一个英文字符占 1 个字节,一个中文汉字通常占 3 个字节,每个字节数值在
0 ~ 255之间,转成十六进制后严格为 2 位(如"e4"、"bd")。 - 场景 B:UTF-16 字符码点十六进制数组:直接获取字符串中每个字符在 JS 内存中的 Code Point(例如通过
charCodeAt)。此时英文字符是 2 位十六进制,而一个汉字会得到一个大于 255 的 16 位码点(如"4f60"),它并不是标准的字节流。
二、标准推荐方案:基于 TextEncoder 的字节转换
如果你需要的是通用的、能够与外部二进制协议兼容的十六进制数组,首选现代浏览器与 Node.js 均内置支持的 TextEncoder API。它会将任意字符串按照国际标准的 UTF-8 编码转换成 Uint8Array 字节数组,随后映射为十六进制。
1. 完整实现代码
/**
* 将字符串转为十六进制字节数组(基于 UTF-8)
* @param {string} str 输入字符串
* @param {boolean} prefix 是否添加 '0x' 前缀
* @param {boolean} uppercase 是否转换为大写
* @returns {string[]} 十六进制字符串数组
*/
function stringToHexArray(str, prefix = false, uppercase = false) {
// 1. 将字符串转为 UTF-8 编码的 Uint8Array
const encoder = new TextEncoder();
const uint8Array = encoder.encode(str);
// 2. 将每个字节转换为 2 位十六进制字符串
return Array.from(uint8Array, byte => {
let hex = byte.toString(16).padStart(2, '0');
if (uppercase) {
hex = hex.toUpperCase();
}
return prefix ? `0x${hex}` : hex;
});
}
// 示例 1:纯英文转换
const asciiResult = stringToHexArray("Hello");
console.log(asciiResult);
// 示例输出: ["68", "65", "6c", "6c", "6f"]
// 示例 2:中英混合转换(带 0x 前缀)
const mixedResult = stringToHexArray("你好", true);
console.log(mixedResult);
// 示例输出: ["0xe4", "0xbd", "0xa0", "0xe5", "0xa5", "bd"]
byte.toString(16):将十进制整数转换为基数为 16 的字符串。例如数字10转成"a"。.padStart(2, '0'):极其重要的边界补齐。若字节数值小于 16(如换行符 10、空格后的控制字符),toString(16)仅会输出单字符"a",调用补零后才能确保是规范的"0a"。Array.from(uint8Array, mapFn):比传统先转普通数组再.map()更快,内存分配一步到位。
三、传统轻量方案:基于 charCodeAt 的码点转换
在很多只需要处理英文、数字、基本标点(ASCII 码范围 0~127)的轻度工具场景中,可以直接遍历字符串中的字符,获取 charCodeAt() 对应的数值并转为十六进制。
1. 码点方案代码实现
/**
* 基于 UTF-16 字符码点的十六进制转换
* @param {string} str 输入字符串
* @returns {string[]} 十六进制字符数组
*/
function stringToHexByCharCode(str) {
const result = [];
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
// 将码点转换为十六进制并做偶数位补零
let hex = code.toString(16);
if (hex.length % 2 !== 0) {
hex = '0' + hex;
}
result.push(hex);
}
return result;
}
// 示例 1:纯英文字符串
console.log(stringToHexByCharCode("ABC"));
// 示例输出: ["41", "42", "43"]
// 示例 2:中文字符串
console.log(stringToHexByCharCode("你好"));
// 示例输出: ["4f60", "597d"]
2. 两种方案的核心差异与本质原理
对比上面的两次输出,你会发现对同一个汉字 "你":
TextEncoder输出的是 3 个单字节:["e4", "bd", "a0"]。charCodeAt输出的是 1 个双字节码点:["4f60"]。
原因在于 JavaScript 内部使用 UTF-16 存储字符串。汉字“你”在 Unicode 里的码点是 U+4F60,在 UTF-16 下恰好用一个 16 位单元表示;但当它被转为互联网与设备通用的 UTF-8 编码传输时,会被编码为 3 字节序列。如果后端或外部设备期望接收的是标准 Byte Array(字节数组),使用 charCodeAt 生成的 "4f60" 会导致对方解析错位或溢出。
四、进阶细节:格式化控制与逆向还原
在实际项目落地中,接收十六进制数组的下游组件通常对输入格式有严格的要求。以下梳理三种最常见的定制需求与双向还原验证方法。
1. 前缀与大小写格式化
某些语言或解析库(如 C/C++ 结构体或硬件协议)要求每个数值必须携带 0x 前缀且字母大写(如 ["0X48", "0X65"]),而有的仅需要纯小写字符(如 ["48", "65"])。
// 格式化参数对照
const raw = "Hi";
// 格式 A(纯小写无前缀):["68", "69"]
const formatA = stringToHexArray(raw, false, false);
// 格式 B(带 0x 前缀大写):["0x68", "0x69"]
const formatB = stringToHexArray(raw, true, false);
2. 逆向还原:从十六进制数组恢复为字符串
验证代码正确性的最好方式就是实现逆向还原。如果正向是使用 TextEncoder,逆向就应该通过 TextDecoder:
/**
* 将十六进制字节数组还原回原始字符串
* @param {string[]} hexArray 十六进制数组,例如 ["e4", "bd", "a0"] 或带 "0x"
* @returns {string} 还原后的原文字符串
*/
function hexArrayToString(hexArray) {
// 1. 将十六进制文本解析为十进制数字并放入 Uint8Array
const bytes = new Uint8Array(hexArray.map(hex => {
// 兼容带 0x 和不带 0x 的情况
const cleanHex = hex.startsWith('0x') || hex.startsWith('0X')
? hex.slice(2)
: hex;
return parseInt(cleanHex, 16);
}));
// 2. 使用 TextDecoder 解码为 UTF-8 字符串
return new TextDecoder().decode(bytes);
}
// 闭环验证:
const originText = "JavaScript 字符串转十六进制";
const hexList = stringToHexArray(originText);
const restoredText = hexArrayToString(hexList);
console.log(originText === restoredText); // 示例输出: true
五、常见问题与踩坑提醒
-
忽略
padStart(2, '0')导致十六进制位数参差不齐:
很多初学者直接写code.toString(16),遇到 ASCII 码小于 16 的字符(例如 Tab 制表符9、换行10)或者字节数值较小的数据时,输出的是单字符"9"、"a"。当下游系统按固定两个字符拆分字节时就会完全乱序。 -
Emoji 与辅助平面字符处理:
在 UTF-16 规范下,一个 Emoji(例如"😊")由两个 16 位的“代理对”(Surrogate Pairs)组成。如果用str.length和charCodeAt处理,会拆散成两个不合法的半码点;而使用TextEncoder则能自动按 UTF-8 标准将其转为标准的 4 字节序列(如["f0", "9f", "98", "8a"]),完全不需要手动处理代理对。 -
运行环境支持度:
TextEncoder和TextDecoder在现代主流浏览器(Chrome、Firefox、Safari、Edge)以及 Node.js 11+ 中均为全局原生对象,无需引入任何第三方 NPM 包即可直接使用。
六、方案选型与技术总结
为了在实际开发中快速抉择,下表对两种核心实现方案进行了横向比对:
| 对比维度 | TextEncoder 方案(推荐) | charCodeAt 方案 |
|---|---|---|
| 编码基准 | UTF-8 物理字节流 | UTF-16 内存字符码点 |
| 多字节中文表现 | 拆分为 3 个单字节十六进制(严格 0x00~0xFF) | 输出 1 个 4 位十六进制码点(如 4f60) |
| Emoji 特殊符号 | 原生正确处理为 4 字节序列 | 拆解为高低代理对,需额外处理 |
| 补零规则 | 固定补齐为 2 位 | 偶数位补齐(2位或4位) |
| 最佳适用场景 | 前后端协议通讯、蓝牙设备交互、哈希加密、文件二进制 | 纯英文字符串简易转换、算法刷题、JS 内部码点分析 |
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。