PHP: 从字符串中提取打包的十六进制数字

4

我正在尝试从字符串中提取打包的十六进制数字。我的应用程序与一个服务器进行通信,该服务器发送带有头部的字符串,后跟2字节的打包的十六进制数字。在这个字符串中有成千上万个数字。

我想要做的是提取每个2字节的压缩数字,并将其转换为我可以用来执行计算的数字。

例如:string = "info:\x00\x00\x11\x11\x22\x22"会生成三个数字0x0000(十进制0),0x1111(十进制4369),0x2222(十进制8738)

我有一个解决方案(见下文),但当我尝试处理服务器发送过来的几千个数字时,它的功能速度太慢了。请提供一些建议以加快我的方法。

```python import struct
def extract_numbers(string): numbers = [] for i in range(0, len(string), 2): byte_pair = string[i:i+2] number = struct.unpack('!H', byte_pair)[0] numbers.append(number) return numbers ```
//Works but is too slow!
//$string has the data from the server
$arrayIndex = 0;
for($index = [start of data]; $index < strlen($string); $index+=2){
    $value = getNum($string, $index, $index+1);
    $array[$arrayIndex++] = $value;
}
function getNum($string, $start, $end){
    //get the substring we're interested in transforming
    $builder = substr($string, $start, $end-$start+1);  

    //convert into hex string
    $array = unpack("H*data", $builder);
    $answer = $array["data"];

    //return the value as a number
    return hexdec($answer);
}

我也一直在尝试使用单个unpack命令提取数字,但这并不起作用(我在理解需要使用的格式字符串方面遇到了一些困难)。

//Not working alternate method
//discard the header (in this case 18 bytes) and put the rest of the
//number values I'm interested in into an array
$unpacked = unpack("c18char/H2*data", $value);
for($i = 0; $i < $size; $i+=1){
    $data = $unpacked["data".$i];
    $array[$i] = $data;
}
3个回答

2
$array = array();
$len = strlen($string);
for($index = [start of data];          $index < $len;               $index+=2){
    $d = unpack("H*data", substr($string, $index, 2));
    $array[] = hexdec($d["data"]);
}

我所做的唯一重要的事情就是缓存strlen的值并减少函数调用。

你也可以尝试这个方法。

foreach (str_split(substr($string, [start of data]), 2) as $chunk) {
    $d = unpack("H*data", $chunk);
    $array[] = hexdec($d["data"]);
}

我计时了您的建议:我的原始代码运行时间为0.099秒,您的第一个示例在0.066秒内运行,第二个示例在0.070秒内运行。因此总体上有约33%的改进!谢谢。 - Gregory Peck
我在之前的评论中颠倒了数字,例如1花费了0.070秒,而示例2花费了0.066秒。 - Gregory Peck

1
我可以建议的一件事是,通过引用而不是值传递包含数千个十六进制数字的字符串。如果有3k个数字,字符串长度为12k个字符,在多次调用3k函数的情况下,在堆栈上分配了约36M(如果每个字符使用一个字节,则为~72M utf8)不必要的内存:
$arrayIndex = 0;
for($index = [start of data]; $index < strlen($string); $index+=2){
    $value = getNum($string, $index, $index+1);
    $array[$arrayIndex++] = $value;
}
 //pass by reference rather than value
function getNum(&$string, $start, $end){
    //get the substring we're interested in transforming
    //$builder = substr($string, $start, $end-$start+1);  
    //not sure if substr takes reference or value, so implementing this way, just in case it's by value
      $builder = $string[$start] . $string[$start + 1] ;
    //convert into hex string
    $array = unpack("H*data", $builder);
    $answer = $array["data"];

    //return the value as a number
    return hexdec($answer);
}

不确定这会加快多少速度(肯定可以加快内存分配),但值得一试。


2
PHP使用写时复制技术,因此按值传递字符串实际上并不会复制整个字符串。 - goat
使用您的建议进行了一些测试,以下是测试结果:我的代码运行时间为0.099秒,使用子字符串的$string的运行时间为0.280秒,使用加法的$string(根据您的建议)运行时间为0.097秒。虽然略有改善,但仍不够理想。还是非常感谢您的帮助! - Gregory Peck

0
为什么不试试这样的东西:
$string = "info:\x00\x00\x11\x11\x22\x22";

$ret = array();
preg_match_all('#\\x(\d{2})#', $string, $items);
if(isset($items[1]) && count($items[1])>0)
{
     for($i=0;$i<count($items[1]);$i+=2)
     {
            if(isset($items[1][$i]) && isset($items[1][$i+1]))
            {
                    $ret[] = '0x' . $items[1][$i] . $items[1][$i+1];
                    unset($items[1][$i]);
                    unset($items[1][$i+1]);
            }
     }
}

出于某种原因,我无法正确地将数据输出到数组中。可能是因为数据是打包的十六进制(所以\d不匹配)。我稍微修改了一下代码,像这样$string = substr($string, [start position]); preg_match_all('#(.{2})#', $value, $items); 这个运行了,但输出不正确。通过我的修改,这个运行时间为0.132秒,而我的原始示例为0.099秒。所以它慢了一点,但还是谢谢你的帮助! - Gregory Peck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接