JavaScript中十六进制字符串到字节值的稳健转换

Robust conversion of hexadecimal string to byte values in JavaScript

本文关键字:转换 到字节 十六进制 字符串 JavaScript      更新时间:2023-09-26

我尝试从包含十六进制字节表示的字符串中提取字节值。该字符串还包含(未知)需要忽略的非十六进制字符(分隔符,空白格式)。

给定一个输入字符串"f5 df 45:f8 a 8 f53",结果将是数组[245, 223, 69, 248, 168, 245]。请注意,字节值只输出两个十六进制数字(因此,最后一个3被忽略)。

作为附加约束,代码需要在ecmascript 3环境中工作。

到目前为止,我一直使用这种方法:
function parseHex(hex){
    hex = hex.replace(/[^0-9a-fA-F]/g, '');
    var i, 
        len = hex.length, 
        bin = [];
    for(i = 0; i < len - 1; i += 2){
        bin.push(+('0x' + hex.substring(i, i + 2)));
    }
    return bin;
}

然而,我觉得有可能找到一个更优雅的解决方案,所以问题是:

是否有更好的解决方案来解决这个问题(将执行得更好或用更少的代码解决问题)?

更新答案(ES3)

既然你在我最初的回答的评论中提到你仅限于ES3,那么你应该能够做到这一点:

function parseHex(string) {
  // remove all non-hex characters, and then separate them into an array in groups of 2 characters
  var arr = string.replace(/[^0-9a-fA-F]/g, '').match(/[0-9a-fA-F]{2}/g);
  // mutate the array in-place with the correct decimal values
  for(var i = 0; i<arr.length; i++) {
    arr[i] = parseInt(arr[i], 16);
  }
  return arr;
}
parseHex('f5 df 45:f8 a 8 f53'); // => [245, 223, 69, 248, 168, 245]

它基本上会做map所做的事情,除了它比map具有更少的空间复杂性,因为它在原地改变数组。查看更新后的jsfiddle

上一个答案(ES5)

你可以这样做(这是一个jsbin的例子):

'f5 df 45:f8 a 8 f53'.replace(/[^0-9a-fA-F]/g, '').match(/[0-9a-fA-F]{2}/g).map(function(hex) {
  return parseInt(hex, 16);
});
// => [245, 223, 69, 248, 168, 245]

你可以把它变成这样的函数:

function parseHex(string) {
  return string.replace(/[^0-9a-fA-F]/g, '').match(/[0-9a-fA-F]{2}/g).map(function(hex) {
    return parseInt(hex, 16);
  });
}
parseHex('f5 df 45:f8 a 8 f53');

本质上是从字符串中删除非十六进制字符,然后匹配两个十六进制字符组(根据您的要求)。这个答案描述了parseInt(hex, 16)部分(相反的是hex.toString(16))。

TL;DR

使用regex方法会导致更少的代码,但性能更差。非正则表达式的解决方案提供了更好的性能,但代价是代码稍微多一些。

Regex方法

经过更多的研究/谷歌搜索(并看到Josh beam使用.match()回答),我认为有几种可能的regex方法可以改进原始方法。

直接使用.match()(不使用.replace()),灵感来自Josh Beams的回答:

function parseHex(hex){
    hex = hex.match(/['da-f]/gi);
    for(var i = 0; i < hex.length - 1; i += 2){
        hex[i >> 1] = +('0x' + hex[i] + hex[i + 1]);
    }
    hex.length = i >> 1;
    return hex;
}

使用.replace()进行迭代(受此启发):

function parseHex(hex){
    var bin = [];
    hex.replace(/(['da-f])[^'da-f]*(['da-f])/gi,
        function(m, digit1, digit2){
            bin.push(+('0x' + digit1 + digit2));
        }
    );
    return bin;
}

使用.exec()循环(同样受到此启发):

function parseHex(hex){
    var bin = [],
        regex = /(['da-f])[^'da-f]*(['da-f])/gi,
        result;
    while(result = regex.exec(hex)){
        bin.push(+('0x' + result[1] + result[2]));
    }
    return bin;
}

性能和非正则表达式解决方案

在这里运行性能测试后,没有任何一种regex方法的性能明显优于原始方法。出于好奇,我尝试了一个非正则表达式的解决方案,它明显优于其他方法(代价是代码稍微多一些):

function parseHex(hex){
    var bin = [], i, c, isEmpty = 1, buffer;
    for(i = 0; i < hex.length; i++){
        c = hex.charCodeAt(i);
        if(c > 47 && c < 58 || c > 64 && c < 71 || c > 96 && c < 103){
            buffer = buffer << 4 ^ (c > 64 ? c + 9 : c) & 15;
            if(isEmpty ^= 1){
                bin.push(buffer & 0xff);
            }
        }
    }
    return bin;
}

我可能会选择非正则表达式的方法