PHP Conference Japan 2024

unpack

(PHP 4, PHP 5, PHP 7, PHP 8)

unpack从二进制字符串解包数据

描述

unpack(字符串 $format, 字符串 $string, 整数 $offset = 0): 数组|false

根据给定的format将二进制字符串解包到数组中。

解包后的数据存储在一个关联数组中。为此,您必须命名不同的格式代码,并用斜杠 / 分隔它们。如果存在重复参数,则每个数组键的后面都会有一个序号。

进行了更改以使此函数与 Perl 保持一致。

  • "a" 代码现在保留尾随 NULL 字节。
  • "A" 代码现在去除所有尾随 ASCII 空白字符(空格、制表符、换行符、回车符和 NULL 字节)。
  • 添加了 "Z" 代码用于 NULL 填充的字符串,并删除尾随 NULL 字节。

参数

format

有关格式代码的说明,请参见pack()

string

打包的数据。

offset

开始解包的偏移量。

返回值

返回包含二进制字符串解包元素的关联数组,或者在失败时返回false

变更日志

版本 描述
7.2.0 浮点数双精度浮点数 类型都支持大端序和小端序。
7.1.0 添加了可选的offset

示例

示例 #1 unpack() 示例

<?php
$binarydata
= "\x04\x00\xa0\x00";
$array = unpack("cchars/nint", $binarydata);
print_r($array);
?>

上面的例子将输出

Array
(
    [chars] => 4
    [int] => 160
)

示例 #2 使用重复器的 unpack() 示例

<?php
$binarydata
= "\x04\x00\xa0\x00";
$array = unpack("c2chars/nint", $binarydata);
print_r($array);
?>

上面的例子将输出

Array
(
    [chars1] => 4
    [chars2] => 0
    [int] => 40960
)

备注

警告

请注意,PHP 在内部将整数值存储为有符号数。如果您解包一个大型无符号长整型,并且它与 PHP 内部存储值的长度相同,则即使指定了无符号解包,结果也将为负数。

警告

如果您没有命名元素,则使用从1开始的数字索引。请注意,如果您有多个未命名的元素,则某些数据会被覆盖,因为每个元素的编号都从1重新开始。

示例 #3 使用未命名键的 unpack() 示例

<?php
$binarydata
= "\x32\x42\x00\xa0";
$array = unpack("c2/n", $binarydata);
var_dump($array);
?>

上面的例子将输出

array(2) {
  [1]=>
  int(160)
  [2]=>
  int(66)
}

请注意,来自c说明符的第一个值被来自n说明符的第一个值覆盖。

参见

  • pack() - 将数据打包到二进制字符串

添加注释

用户贡献的注释 14 条注释

stanislav dot eckert at vizson dot de
8 年前
一个辅助类,用于将整数转换为二进制字符串,反之亦然。这对于向文件或套接字写入和读取整数非常有用。

<?php

class int_helper
{
public static function
int8($i) {
return
is_int($i) ? pack("c", $i) : unpack("c", $i)[1];
}

public static function
uInt8($i) {
return
is_int($i) ? pack("C", $i) : unpack("C", $i)[1];
}

public static function
int16($i) {
return
is_int($i) ? pack("s", $i) : unpack("s", $i)[1];
}

public static function
uInt16($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";

if (
$endianness === true) { // 大端序
$i = $f("n", $i);
}
else if (
$endianness === false) { // 小端序
$i = $f("v", $i);
}
else if (
$endianness === null) { // 机器字节序
$i = $f("S", $i);
}

return
is_array($i) ? $i[1] : $i;
}

public static function
int32($i) {
return
is_int($i) ? pack("l", $i) : unpack("l", $i)[1];
}

public static function
uInt32($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";

if (
$endianness === true) { // 大端序
$i = $f("N", $i);
}
else if (
$endianness === false) { // 小端序
$i = $f("V", $i);
}
else if (
$endianness === null) { // 机器字节序
$i = $f("L", $i);
}

return
is_array($i) ? $i[1] : $i;
}

public static function
int64($i) {
return
is_int($i) ? pack("q", $i) : unpack("q", $i)[1];
}

public static function
uInt64($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";

if (
$endianness === true) { // 大端序
$i = $f("J", $i);
}
else if (
$endianness === false) { // 小端序
$i = $f("P", $i);
}
else if (
$endianness === null) { // 机器字节序
$i = $f("Q", $i);
}

return
is_array($i) ? $i[1] : $i;
}
}
?>

使用示例
<?php
Header
("Content-Type: text/plain");
include(
"int_helper.php");

echo
int_helper::uInt8(0x6b) . PHP_EOL; // k
echo int_helper::uInt8(107) . PHP_EOL; // k
echo int_helper::uInt8("\x6b") . PHP_EOL . PHP_EOL; // 107

echo int_helper::uInt16(4101) . PHP_EOL; // \x05\x10
echo int_helper::uInt16("\x05\x10") . PHP_EOL; // 4101
echo int_helper::uInt16("\x05\x10", true) . PHP_EOL . PHP_EOL; // 1296

echo int_helper::uInt32(2147483647) . PHP_EOL; // \xff\xff\xff\x7f
echo int_helper::uInt32("\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 2147483647

// 注意:使用 64 位 PHP 版本测试此代码
echo int_helper::uInt64(9223372036854775807) . PHP_EOL; // \xff\xff\xff\xff\xff\xff\xff\x7f
echo int_helper::uInt64("\xff\xff\xff\xff\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 9223372036854775807

?>
Sergio Santana: ssantana at tlaloc dot imta dot mx
20 年前
这是关于我上一篇文章的最后一个例子。为了清晰起见,我在这里再次包含此示例,它扩展了正式文档中给出的示例。

<?
$binarydata = "AA\0A";
$array = unpack("c2chars/nint", $binarydata);
foreach ($array as $key => $value)
echo "\$array[$key] = $value <br>\n";
?>

输出结果:

$array[chars1] = 65
$array[chars2] = 65
$array[int] = 65

这里,我们假设字符 'A' 的 ASCII 码为十进制 65。

记住格式字符串结构是:
<format-code> [<count>] [<array-key>] [/ ...],
在这个例子中,格式字符串指示函数:
1. ("c2...") 从第二个参数 ("AA ...") 读取两个字符,
2. (...chars...) 使用数组键“chars1”和“chars2”来表示
这两个字符的读取结果。
3. (.../n...) 从第二个参数读取一个短整型数 (...\0A"),
4. (...int") 使用单词“int”作为刚刚读取的
短整型的数组键。

我希望现在更清楚了。

塞尔吉奥。
jjfoerch at earthlink dot net
20 年前
我遇到一种情况,需要解压一个包含小端序双精度浮点数的文件,并且需要在小端序或大端序机器上都能工作。PHP 没有可以更改双精度浮点数字节序的格式代码,所以我编写了这个变通方法。

<?php
/*以下代码是针对 php 的 unpack 函数的变通方法,该函数无法解压以与当前机器相反的字节序打包的双精度浮点数。
*/
function big_endian_unpack ($format, $data) {
$ar = unpack ($format, $data);
$vals = array_values ($ar);
$f = explode ('/', $format);
$i = 0;
foreach (
$f as $f_k => $f_v) {
$repeater = intval (substr ($f_v, 1));
if (
$repeater == 0) $repeater = 1;
if (
$f_v{1} == '*')
{
$repeater = count ($ar) - $i;
}
if (
$f_v{0} != 'd') { $i += $repeater; continue; }
$j = $i + $repeater;
for (
$a = $i; $a < $j; ++$a)
{
$p = pack ('d',$vals[$i]);
$p = strrev ($p);
list (
$vals[$i]) = array_values (unpack ('d1d', $p));
++
$i;
}
}
$a = 0;
foreach (
$ar as $ar_k => $ar_v) {
$ar[$ar_k] = $vals[$a];
++
$a;
}
return
$ar;
}

list (
$endiantest) = array_values (unpack ('L1L', pack ('V',1)));
if (
$endiantest != 1) define ('BIG_ENDIAN_MACHINE',1);
if (
defined ('BIG_ENDIAN_MACHINE')) $unpack_workaround = 'big_endian_unpack';
else
$unpack_workaround = 'unpack';
?>

这个变通方法的使用方法如下:

<?php

function foo() {
global
$unpack_workaround;
$bar = $unpack_workaround('N7N/V2V/d8d',$my_data);
//...
}

?>

在小端序机器上,`$unpack_workaround` 将简单地指向 `unpack` 函数。在大端序机器上,它将调用变通函数。

注意,此解决方案仅适用于双精度浮点数。在我的项目中,不需要检查单精度浮点数。
kennwhite dot nospam at hotmail dot com
20 年前
如果使用/需要基于零的索引,则不要使用

`$int_list = unpack("s*", $some_binary_data);`

请尝试

`$int_list = array_merge(unpack("s*", $some_binary_data));`

这将返回一个基于 0 的数组

`$int_list[0] = x`
`$int_list[1] = y`
`$int_list[2] = z`
...

而不是 `unpack` 函数在没有提供键的情况下返回的默认基于 1 的数组

`$int_list[1] = x`
`$int_list[2] = y`
`$int_list[3] = z`
...

它不常使用,但是只有一个参数的 `array_merge()` 将压缩一个连续排序的数字索引,从索引 [0] 开始。
匿名
15 年前
在处理固定宽度文件处理时,我发现对 `unpack`/`pack` 函数有用的函数。
<?php
/**
* funpack
* format: 键值对数组,键为名称,值为长度
* data: 要解压的字符串
*/
function funpack($format, $data){
foreach (
$format as $key => $len) {
$result[$key] = trim(substr($data, $pos, $len));
$pos+= $len;
}
return
$result;
}

/**
* fpack
* format: 键值对数组,键为名称,值为长度
* data: 要打包的键值对数组
* pad: 填充方向
*/
function fpack($format, $data, $pad = STR_PAD_RIGHT){
foreach (
$format as $key => $len){
$result .= substr(str_pad($data[$key], $len, $pad), 0, $len);
}
return
$result;
}
?>
Sergio Santana: ssantana at tlaloc dot imta dot mx
20 年前
假设我们需要获取整数(例如 65)的某种内部表示形式,作为四个字节的长整型。然后我们使用类似以下内容:

<?
`$i = 65;`
`$s = pack("l", $i); // 32 位长整型,机器字节序`
`echo strlen($s) . "<br>\n";`
`echo "***$s***<br>\n";`
?>

输出结果是

`X-Powered-By: PHP/4.1.2`
`Content-type: text/html`

4
`***A***`

(即字符串 "A\0\0\0")

现在我们想从字符串 "A\0\0\0" 返回到数字 65。在这种情况下,我们可以使用

<?
`$s = "A\0\0\0"; // 此字符串是数字 65 的字节表示形式`
`$arr = unpack("l", $s);`
`foreach ($arr as $key => $value)`
`echo "\$arr[$key] = $value<br>\n";`
?>

输出结果是
`X-Powered-By: PHP/4.1.2`
`Content-type: text/html`

`$arr[] = 65`

让我们给数组键命名,例如 "mykey"。在这种情况下,我们可以使用

<?
`$s = "A\0\0\0"; // 此字符串是数字 65 的字节表示形式`
`$arr = unpack("lmykey", $s);`
`foreach ($arr as $key => $value)`
`echo "\$arr[$key] = $value\n";`
?>

输出结果是
`X-Powered-By: PHP/4.1.2`
`Content-type: text/html`

`$arr[mykey] = 65`

`unpack` 函数的文档有点令人困惑。我认为更完整的示例可能是:

<?
$binarydata = "AA\0A";
$array = unpack("c2chars/nint", $binarydata);
foreach ($array as $key => $value)
echo "\$array[$key] = $value <br>\n";
?>

其输出结果是:

`X-Powered-By: PHP/4.1.2`
`Content-type: text/html`

`$array[chars1] = 65 <br>`
`$array[chars2] = 65 <br>`
`$array[int] = 65 <br>`

注意,格式字符串类似于:
`<format-code> [<count>] [<array-key>] [/ ...]`

我希望这可以澄清一些问题。

塞尔吉奥
ludwig at kni-online dot de
4年前
不要忘记在解压之前解码用户定义的伪字节序列……
<?php
$byte_code_string
= '00004040';
var_dump ( unpack ( 'f', $byte_code_string ) );
?>
结果
array(1) {
[1]=>
float(6.4096905560973E-10)
}


<?php
$byte_code_string
= '00004040';
var_dump ( unpack ( 'f', hex2bin ( $byte_code_string ) ) );
?>
结果
array(1) {
[1]=>
float(3)
}
Aaron Wells
14年前
将二进制数据转换为PHP数据类型的另一种方法是使用Zend Framework的Zend_Io_Reader类。
http://bit.ly/9zAhgz

还有一个Zend_Io_Writer类可以执行反向操作。
匿名懦夫
16年前
警告:此unpack函数生成的数组键从1开始,而不是从0开始。

例如
<?php
function read_field($h) {
$a=unpack("V",fread($h,4));
return
fread($h,$a[1]);
}
?>
rogier
13年前
请注意PHP所在系统的行为。

在x86系统上,unpack对于UInt32可能不会产生您期望的结果。

这是由于PHP的内部特性,整数在内部存储为有符号数!

对于x86系统,unpack('N', "\xff\xff\xff\xff")的结果为-1。
对于(大多数?)x64系统,unpack('N', "\xff\xff\xff\xff")的结果为4294967295。

可以通过检查PHP_INT_SIZE的值来验证这一点。
如果此值为4,则表示PHP在内部存储32位整数。
值为8表示内部存储64位整数。

为了解决这个问题,可以使用以下代码来避免unpack的问题。
此代码适用于大端序,但可以轻松调整为小端序(类似的代码也适用于64位整数)。

<?php
function _uint32be($bin)
{
// $bin是表示整数的32位大端序二进制字符串
if (PHP_INT_SIZE <= 4){
list(,
$h,$l) = unpack('n*', $bin);
return (
$l + ($h*0x010000));
}
else{
list(,
$int) = unpack('N', $bin);
return
$int;
}
}
?>

请注意,您也可以使用sprintf('%u', $x)来显示无符号的实际值。
另请注意,(至少当PHP_INT_SIZE = 4时)当输入大于0x7fffffff时,结果将是一个浮点值(只需使用gettype检查即可)。

希望这对大家有所帮助。
norwood at computer dot org
14年前
从Excel电子表格读取文本单元格返回一个包含低位嵌入空字符的字符串:0x4100 0x4200 等。为了删除空字符,使用了

<?php
$strWithoutNulls
= implode( '', explode( "\0", $strWithNulls ) );
?>

(unpack()在这里似乎帮助不大;需要字符来重新构成字符串,而不是整数。)
googlybash24 at aol dot com
12年前
要将大端序转换为小端序,或将小端序转换为大端序,请使用以下方法作为示例。

<?php
// file_get_contents() 返回二进制值,unpack("V*", _ ) 返回一个无符号长整型 32 位小端序十进制值,但如果单独使用 bin2hex() 则只会给出文件中的十六进制数据,因此我们使用:
// file_get_contents(),unpack("V*", _ ),然后 dechex(),按此顺序,以获得字节交换效果。
?>

通过此示例中方法的逻辑,您可以发现如何根据需要交换字节序。
sica at wnet com br
15 年前
下面的脚本是一个示例,说明如何将多个值保存在文件中,并用“\r\n”分隔它们,以及如何恢复这些值。

<?php
// 将两个整数值保存到二进制文件中
$nomearq = "./teste.bin";
$valor = 123;
$ptrarq = fopen($nomearq, "wb");
$valorBin = pack("L",$valor);
echo
"第一个值 ($valor) 已打包,大小为 ";
echo
fwrite($ptrarq, $valorBin)." 字节<br>";
echo
"分隔符 \\r\\n,大小为 ";
echo
fwrite($ptrarq, "\r\n")." 字节<br>";
$valor = 456;
$valorBin = pack("L",$valor);
echo
"第二个值 ($valor) 已打包,大小为 ";
echo
fwrite($ptrarq, $valorBin)." 字节<br>";
fclose($ptrarq);

// 恢复已保存的值
$ptrarq = fopen($nomearq, "rb");
$valorBin = file($nomearq,filesize($nomearq));
echo
"<br>读取的值为:<br>";
foreach(
$valorBin as $valor){
$valor = unpack("L",$valor);
print_r($valor);
echo
"<br>";
}
fclose($ptrarq);
?>

结果
第一个值 (123) 已打包,大小为 4 字节
分隔符 \r\n,大小为 2 字节
第二个值 (456) 已打包,大小为 4 字节

读取的值为
Array ( [1] => 123 )
Array ( [1] => 456 )
iredden at redden dot on dot ca
24年前
<?php

function parse_pascalstr($bytes_parsed, $parse_str) {
$parse_info = unpack("x$bytes_parsed/cstr_len", $parse_str);
$str_len = $parse_info["str_len"];
$bytes_parsed = $bytes_parsed + 1;
$parse_info = unpack("x$bytes_parsed/A".$str_len."str", $parse_str);
$str = $parse_info["str"];
$bytes_parsed = $bytes_parsed + strlen($str);

return array(
$str, $bytes_parsed);
}

?>
To Top