首頁 php教程 php手册 PHP: chr和pack、unpack那些事

PHP: chr和pack、unpack那些事

Jun 06, 2016 pm 08:01 PM
php

引子 我之前有篇文详细介绍过pack和unpack:PHP: 深入pack/unpack,如果有不明白的地方,建议再回过头去看多几遍。现在应该能够写出以下代码: ?php echo pack( C , 97 ). \n ; $ php-ftest.phpa 但是,为什么会输出'a'呢?虽然我们知道字符'a'的ASCII码就是

引子

我之前有篇文详细介绍过pack和unpack:PHP: 深入pack/unpack,如果有不明白的地方,建议再回过头去看多几遍。现在应该能够写出以下代码:

<span><?php </span>
<span>echo</span> pack(<span>"C"</span>, <span>97</span>) . <span>"\n"</span>;</span>
登入後複製
<span>$ </span>php -f test.php
a
登入後複製

但是,为什么会输出'a'呢?虽然我们知道字符'a'的ASCII码就是97,但是pack方法返回的是二进制字符串,为什么不是输出一段二进制而是'a'?为了确认pack方法返回的是一段二进制字符串,这里我对官方的pack的描述截了个图:

PHP: chr和pack、unpack那些事

确实如此,pack返回包含二进制字符串的数据,接下来详细进行分析。

程序是如何显示字符的

这里所说的'程序',其实是个宏观的概念。

对于在控制台中执行脚本(这里是指PHP作为cli脚本来执行),脚本的输出会写入标准输出(stdin)或标准错误(stderr),当然也有可能会重定向到某个文件描述符。拿标准输出来说,暂且忽略它是行缓冲、全缓冲或者是无缓冲。脚本进程执行完毕后如果有输出则会在控制台上输出字符串。那这里的控制台就是所说的'程序'。

对于Web来说(这里是指PHP作为Web的服务器端语言),程序执行完后会将结果响应给浏览器或其它UserAgent,为了方便描述,这里统一称为UserAgent。这里的UserAgent就是所说的'程序'。

当然还有其它情况,比如在GUI窗口中的输出,编辑器打开一个文件等等,这都涉及到如何显示字符串的问题。

在控制台中执行

控制台通过shell命令来执行脚本,它会fork一个子进程,之后通过exec替换子进程的地址空间,因为这个子进程不是会话首进程,所以它可以关联到终端。脚本输出执行完毕后退出,回到控制台。来看下面的例子:

<span><?php </span>
<span>$str </span>= <span>'回'</span>;
<span>echo</span> <span>$str </span>. <span>"\n"</span>;</span>
登入後複製
<span>$ </span>php -f test.php
回
登入後複製

test.php是UTF-8格式的文件,我的Linux系统的Locales是zh_CN.UTF-8。

<span>$ </span>locale
<span>LANG</span>=zh_CN.<span>UTF</span>-<span>8</span>
<span>LANGUAGE</span>=
<span>LC_CTYPE</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_NUMERIC</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_TIME</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_COLLATE</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_MONETARY</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_MESSAGES</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_PAPER</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_NAME</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_ADDRESS</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_TELEPHONE</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_MEASUREMENT</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_IDENTIFICATION</span>=<span>"zh_CN.UTF-8"</span>
<span>LC_ALL</span>=
登入後複製

回到刚才的代码,test.php是UTF-8编码的文件,汉字'回'是三个字节表示的UTF8字符(如果不明白,可以看我的另一篇文章: JavaScript: 详解Base64编码和解码 ),所以test.php文件的内容保存在硬盘上的数据就是4个字节('\n'是ASCII字符,用1个字节表示)。将test.php输出时,将这4个字节发送到标准输出,之后被冲洗(这里忽略掉被flush的时机),由控制台来显示。回想一下Linux系统上的locale设置,很显然是采用UTF8的机制来显示字符,所以前三个字节被当成一个UTF8字符,它被组合在一起转成Unicode码然后查表,再显示出来。

<span><?php </span>
<span>$str </span>= <span>'回'</span>;
<span>echo</span> <span>$str </span>. <span>"\n"</span>;
<span>echo</span> <span>$str</span>{<span>0</span>} . <span>$str</span>{<span>1</span>} . <span>$str</span>{<span>2</span>} . <span>"\n"</span>;</span>
登入後複製
<span>$ </span>php -f test.php
回
回
登入後複製

可以看到,不管是整个字符输出,还是三个字节连在一起输出,结果是一样的。我们接下来看看不同平台上同一个字符的Unicode编码和UTF-8编码是否一样:

PHP测试: 

<span><?php </span>
<span>$str </span>= <span>'回'</span>;
<span>$bin </span>= pack(<span>"C3"</span>, ord(<span>$str</span>{<span>0</span>}), ord(<span>$str</span>{<span>1</span>}), ord(<span>$str</span>{<span>2</span>}));
<span>$hex </span>= strtoupper(bin2hex(<span>$bin</span>));
<span>echo</span> <span>"UTF-8编码: "</span> . <span>$hex </span>. <span>"\n"</span>;
<span>/**
* 1110xxxx 10xxxxxx 10xxxxxx
*/</span>
<span>$byte1 </span>= ord(<span>$str</span>{<span>0</span>});
<span>$byte2 </span>= ord(<span>$str</span>{<span>1</span>});
<span>$byte3 </span>= ord(<span>$str</span>{<span>2</span>});
<span>$c1    </span>= ((<span>$byte1 </span>& <span>0x0F</span>) 4</span>) | ((<span>$byte2 </span>& <span>0x3F</span>) >> <span>2</span>);
<span>$c2    </span>= ((<span>$byte2 </span>& <span>0x03</span>) 6) | (<span>$byte3 </span>& <span>0x3F</span>);
<span>$dec   </span>= ((<span>$c1 </span>& <span>0x00FF</span>) 8) | <span>$c2</span>;
<span>echo</span> <span>"Unicode编码: "</span> . <span>$dec </span>. <span>"\n"</span>;
登入後複製
<span>$ </span>php -f test.php
<span>UTF</span>-<span>8</span>编码<span>:</span> <span>E59B9E</span>
<span>Unicode</span>编码<span>:</span> <span>22238</span>
登入後複製

JavaScript测试: 

<span>script</span> <span>type</span>=<span>"text/javascript"</span>><span>
<span>/**
* UTF16和UTF8转换对照表
* U+00000000 – U+0000007F 	0xxxxxxx
* U+00000080 – U+000007FF 	110xxxxx 10xxxxxx
* U+00000800 – U+0000FFFF 	1110xxxx 10xxxxxx 10xxxxxx
* U+00010000 – U+001FFFFF 	11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
* U+00200000 – U+03FFFFFF 	111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
* U+04000000 – U+7FFFFFFF 	1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
*/</span>
<span>var</span> code = (<span>'回'</span>).charCodeAt(<span>0</span>);
<span>// 1110xxxx</span>
<span>var</span> byte1 = <span>0xE0</span> | ((code >> <span>12</span>) & <span>0x0F</span>);
<span>// 10xxxxxx</span>
<span>var</span> byte2 = <span>0x80</span> | ((code >> <span>6</span>) & <span>0x3F</span>);
<span>// 10xxxxxx</span>
<span>var</span> byte3 = <span>0x80</span> | (code & <span>0x3F</span>);

console.group(<span>'Test chr: '</span>);
console.log(<span>"UTF-8编码:"</span>, byte1.toString(<span>16</span>).toUpperCase() + <span>''</span> + byte2.toString(<span>16</span>).toUpperCase() + <span>''</span> + byte3.toString(<span>16</span>).toUpperCase());
console.log(<span>"Unicode编码: "</span>, code);
console.groupEnd();
</span><span><span>script</span>></span>
登入後複製

PHP: chr和pack、unpack那些事

我们看到输出是一样的。

作为Web的服务器端语言执行

这次无非是由刚才的控制台执行变成了UserAgent,其实道理还是一样的。服务器端PHP脚本输出会通过HTTP的响应返回给UserAgent,那么UserAgent就要对它进行显示。当然,这里还有点例外。数据是通过网络作为字节流发送回UserAgent,通常UserAgent有几种方式来判断字节流是属于什么编码(或许还涉及到压缩,但这里将不考虑这个因素)。

服务器端可以通过响应头部来告诉UserAgent应该用什么编码来处理这些数据,比如:

<span><?php header("Content-Type: text/html; charset=utf8");</span></span>
登入後複製
<span>meta charset="utf-8" </span>/>
登入後複製

但是万一这两种方式都没有提供,那也只能靠猜了。事实也确实如此,据我所知,Firefox就是这么做的,并且将代码开源了: universalchardet 。但是这种方式并不能百分之百正确检测,所以偶尔会访问到乱码的页面。

编辑器打开一个文件

在windows上用notepad新建文本文件另存为时有几种编码选项:ANSI, Unicode, Unicode BigEndian, UTF-8。

PHP: chr和pack、unpack那些事

在其它编辑器中选项更多,包括有BOM和无BOM的。BOM是文件头的前几个字节,通过BOM,处理它的程序就知道这个文件是采用什么编码,并且是什么字节序。然而在PHP中,从来都没有将BOM考虑进去,所以PHP解释器去执行一个PHP文件时,不会忽略前几个BOM字节,这就导致了问题。一般的问题在于发送cookie前,BOM被输出了。所以现在一般推荐无BOM的文件。

无BOM有时候也是会有问题的,因为这需要处理它的程序去检测它是什么编码。检测的方式一般是扫描文件,然后根据不同编码的规则来判断二进制。这里举一个出现问题的例子。在windows上新建一个文本文件并保存为ANSI编码,然后在文件中输入'联通',如图所示:

PHP: chr和pack、unpack那些事

保存好后关闭test.txt文件,然后再双击打开,如图所示:

PHP: chr和pack、unpack那些事

我们看到显示的是乱码,具体我们可以分析一下产生乱码的原因。用Editplus新建一个ANSI文件,输入'联通',然后切换到十六进制查看方式,如下图所示:

PHP: chr和pack、unpack那些事

对应的十六进制是:C1 AA CD A8,转成二进制后如下:

11000001 10101010 11001101 10101000
登入後複製

接着我们来看下UTF-8的转换表:

U+<span>00000000</span> – U+<span>0000007F</span> 	<span>0</span>xxxxxxx
U+<span>00000080</span> – U+<span>000007F</span>F 	<span>110</span>xxxxx <span>10</span>xxxxxx
U+<span>00000800</span> – U+<span>0000F</span>FFF 	<span>1110</span>xxxx <span>10</span>xxxxxx <span>10</span>xxxxxx
U+<span>00010000</span> – U+<span>001F</span>FFFF 	<span>11110</span>xxx <span>10</span>xxxxxx <span>10</span>xxxxxx <span>10</span>xxxxxx
U+<span>00200000</span> – U+<span>03F</span>FFFFF 	<span>111110</span>xx <span>10</span>xxxxxx <span>10</span>xxxxxx <span>10</span>xxxxxx <span>10</span>xxxxxx
U+<span>04000000</span> – U+<span>7F</span>FFFFFF 	<span>1111110</span>x <span>10</span>xxxxxx <span>10</span>xxxxxx <span>10</span>xxxxxx <span>10</span>xxxxxx <span>10</span>xxxxxx
登入後複製

很显然都被当作了二字节的UTF-8字符,拿GBK的编码去UTF-8的码表里查,您说能查到吗?

总结

现在我们已经知道了不管是什么编码的数据,总是一个字节一个字的存储,并且在存储时会进行相应的编码转换。比如汉字'回'的GBK编码和UTF-8编码的字节数和编码值都不一样,所以在将GBK的文件另存为UTF-8时必然会存在转换,反之也是一样的。而在读取时如果有BOM就按BOM规定的编码来处理,否则要进行编码检测后再处理。

再说pack

之前讲了这么多编码方面的问题,其实就是为了让大家更好的理解接下来要讲的。pack可以将ASCII进行打包然后输出(事实上就是将一个多字节变成多个单字节,之后可以通过unpack转换回来),这个我们已经知道了。但是方式是有很多种,原理是一样的。我们来详细分析。对pack/unpack不太熟悉的还是建议去翻看我之前的一篇文章:PHP: 深入pack/unpack 。因为本人的机器是小端序的,所以本文只考虑小端序。大端序是一样的方式,只不过字节序不一样罢了,可以先判断本机的字节序再处理。

<span><?php </span>
<span>echo</span> pack(<span>"C"</span>, <span>0x61</span>) . <span>"\n"</span>;
<span>echo</span> pack(<span>"S"</span>, <span>0x6161</span>) . <span>"\n"</span>;
<span>echo</span> pack(<span>"L"</span>, <span>0x61616161</span>) . <span>"\n"</span>;
<span>echo</span> pack(<span>"L"</span>, <span>0x9E9BE561</span>) . <span>"\n"</span>;
<span>echo</span> chr(<span>0xE5</span>) . chr(<span>0x9B</span>) . chr(<span>0x9E</span>) . <span>"\n"</span>;
<span>echo</span> pack(<span>"H6"</span>, <span>"E59B9E"</span>) . <span>"\n"</span>;</span>
登入後複製
<span>$ </span>php -f test.php
a
aa
aaaa
a回
回
回
登入後複製

我们一句句的来分析,首先是:

<span>echo</span> pack(<span>"C"</span>, 0x61) . <span>"\n"</span>;
<span>echo</span> pack(<span>"S"</span>, 0x6161) . <span>"\n"</span>;
<span>echo</span> pack(<span>"L"</span>, 0x61616161) . <span>"\n"</span>;
登入後複製

这三句代码很简单,C是无符号字节,S是2个无符号字节,L是4个无符号字节,所以输出也没什么疑问。无论几个字节,都是ASCII码,0x61的二进制的高位为0,所以能正确显示。

<span>echo</span> pack(<span>"L"</span>, 0x9E9BE561) . <span>"\n"</span>;
登入後複製

我们或许还记得汉字'回'的UTF-8编码为:0xE59B9E,L是按主机字节序打包的,而我的机器是小端序,所以0x9E9BE561打包后就变为:0x61E59B9E。0x61是字符'a'的ASCII码,而后面的三个字节程序通过判断0xE5就能知道这是一个三字节的UTF-8字符,因此这三个字节会转成Unicode码去查表,然后显示。

echo <span>chr</span>(<span>0xE5</span>) . <span>chr</span>(<span>0x9B</span>) . <span>chr</span>(<span>0x9E</span>) . <span>"\n"</span>;
登入後複製

chr是返回ASCII码所代码的字符,它其实不仅仅是转换单字节的字符,对于多字节同样适用。它会根据刚才所说的规则将三个UTF-8字节转成Unicode码然后去查表。

<span>echo</span> pack(<span>"H6"</span>, <span>"E59B9E"</span>) . <span>"\n"</span>;
登入後複製

对于H格式字符,它和h的区别就是前者是高四位在前,后者是低四位在前,但它们都是以半字节为单位读取的,并且以十六进制的方式。您应该看到我在用H进行打包时传的是字符串"E59B9E",如果传的是0xE59B9E就不对了,这样的话先会转成十进制15047582,然后在前面加上0x变成十六进制0x15047582。

所谓按半字节读取其实是这样的,比如0x47,先转成十进制71,然后变成十六进制的0x71。按半字节读取必然会丢弃4位,然后要补0。读取了0x7,对H来说,它是高位,那么在低位补0变成0x70。对于h来说,它是低位,那么在高位补0变成0x07。

再说unpack

unpack是pack的逆函数,当然unpack有自己的语法,但这不是重点,因为这些只是表象。

unpack其实只是将多个字节压缩成一个字节。比如0x12和0x34这两个字节如果要组成一个双字节,则可以使用unpack的S格式化字符来实现,代码如下:

<span><?php </span>
<span>$data </span>= unpack(<span>"S"</span>, pack(<span>"H*"</span>, <span>"3412"</span>));
print_r(<span>$data</span>);
<span>echo</span> <span>'0x'</span> . dechex(<span>$data</span>[<span>1</span>]) . <span>"\n"</span>;</span>
登入後複製
<span>$ php </span>-f test.php
<span>Array</span>
(
    [<span>1</span>] => <span>4660</span>
)
<span>0x1234</span>
登入後複製

因为是小端序,所以要写成"3421"。其实还可以用位运算的方式来实现。这个时候就不需要考虑字节序了,因为字节序只是存储时才需要考虑的问题,对于输出来说,是按照我们自然的方式:

<span><?php </span>
<span>print</span> <span>"0x"</span> . dechex((<span>0x12</span> 8</span>) | <span>0x34</span>) . <span>"\n"</span>;
登入後複製
<span>$ </span>php -f test.php
<span>0x1234</span>
登入後複製

再说chr

PHP官方文档上所描述的chr方法的原型参数是一个int型,虽然形参名为ascii,但不要被骗了。如图所示:

PHP: chr和pack、unpack那些事

chr确实是可以接收一个int类型的参数,而不仅仅是一个ASCII码。还记得之前所做的测试吗?通过chr方法将三个UTF-8的字节组合在一起。很显然UTF-8的每个字节都大于127,因为最高位都是1。

不过说起来chr方法还是比较傻的,比如有如下代码:

<span><?php </span>
<span>echo</span> chr(<span>0xE5</span>) . chr(<span>0x9B</span>) . chr(<span>0x9E</span>) . <span>"\n"</span>;
<span>echo</span> chr(<span>0xE59B9E</span>) . <span>"\n"</span>;</span>
登入後複製
<span>$ </span>php -f test.php
回
?
登入後複製

chr方法完全没有考虑将0xE59B9E拆成三个字节来组合,所以最终是乱码。

再说ord

ord接受一个string类型的参数,它用于返回参数的ASCII码。如下图所示:

PHP: chr和pack、unpack那些事

虽然它只返回ASCII码,但它的参数却不限定。比如您可以传递单字节或多字节。举例如下:

<span><?php </span>
<span>echo</span> ord(<span>"a"</span>) . <span>"\n"</span>;
<span>echo</span> ord(<span>"回"</span>) . <span>"\n"</span>;
<span>echo</span> <span>0xE5</span> . <span>"\n"</span>;</span>
登入後複製
<span>$ </span>php -f test.php
<span>97</span>
<span>229</span>
<span>229</span>
登入後複製

传入汉字'回',它会自动截取第一个字节,然后返回它的十进制表示。

实现自己的pack

理解了原理,其实自己去实现也就是那么回事。本文以格式化字符L为例,L是无符号32位整型,它是按主机字节序来打包的。所以我们要先判断机器的字节序。

<span><?php </span>
<span><span>function</span> <span>IsBigEndian</span><span>()</span>
{</span>
<span>$bin </span>= pack(<span>"L"</span>, <span>0x12345678</span>);
<span>$hex </span>= bin2hex(<span>$bin</span>);
<span>if</span> (ord(pack(<span>"H2"</span>, <span>$hex</span>)) === <span>0x78</span>)
{
<span>return</span> <span>FALSE</span>;
}

<span>return</span> <span>TRUE</span>;
}

<span>if</span> (IsBigEndian())
{
<span>echo</span> <span>"大端序"</span>;
}
<span>else</span>
{
<span>echo</span> <span>"小端序"</span>;
}

<span>echo</span> <span>"\n"</span>;</span>
登入後複製
<span>$ </span>php -f test.php
小端序
登入後複製

代码非常简单,因为PHP不能直接操作内存,所以借助于pack来实现。L格式化字符表示主机字节序,如果机器是小端序,则0x12345678通过L打包后会变成4个字节并且字节序是:0x78, 0x56, 0x34, 0x12,如果是大端序则是:0x12, 0x34, 0x56, 0x78。然后通过H2格式化字符获取1个高字节,如果是0x78那就是小端序,否则就是大端序。

接下来是my_pack方法的实现,仅仅实现了L格式化字符,代码如下:

<?php function IsBigEndian()
{
<span>$bin = <span>pack</span>(<span>"L"</span>, <span>0x12345678</span>);
<span>$hex</span> = bin2hex(<span>$bin</span>);
<span>if</span> (<span>ord</span>(<span>pack</span>(<span>"H2"</span>, <span>$hex</span>)) === <span>0x78</span>)
{
<span>return</span> FALSE;
}

<span>return</span> TRUE;
}

function my_pack(<span>$num</span>)
{
<span>$bin</span>     = <span>""</span>;
<span>$padding</span> = <span>0</span>;
<span>if</span> (<span>$num</span> >= <span>0x00</span> && <span>$num</span> 0xFF)
{
<span>//</span> 补<span>3</span>个字节
<span>$padding</span> = str_repeat(<span>chr</span>(<span>0</span>), <span>3</span>);
<span>if</span> (!IsBigEndian())
{
<span>$bin</span> = <span>chr</span>(<span>$num</span>) . <span>$padding</span>;
}
<span>else</span>
{
<span>$bin</span> = <span>$padding</span> . <span>chr</span>(<span>$num</span>);
}
}
<span>else</span> <span>if</span> (<span>$num</span> > <span>0xFF</span> && <span>$num</span> 0xFFFF)
{
<span>//</span> 补<span>2</span>个字节
<span>$padding</span> = str_repeat(<span>chr</span>(<span>0</span>), <span>2</span>);
<span>$byte3</span>   = (<span>$num</span> >> <span>8</span>) & <span>0xFF</span>;
<span>$byte4</span>   = <span>$num</span> & <span>0xFF</span>;
<span>//</span> 如果是小端序,则按小端序方式
<span>if</span> (!IsBigEndian())
{
<span>$bin</span> = <span>chr</span>(<span>$byte4</span>) . <span>chr</span>(<span>$byte3</span>) . <span>$padding</span>;
}
<span>else</span>
{
<span>$bin</span> = <span>$padding</span> . <span>chr</span>(<span>$byte3</span>) . <span>chr</span>(<span>$byte4</span>);
}
}
<span>else</span> <span>if</span> (<span>$num</span> > <span>0xFFFF</span> && <span>$num</span> 0x7FFFFF)
{
<span>//</span> 补<span>1</span>个字节
<span>$padding</span> = <span>chr</span>(<span>0</span>);
<span>$byte2</span>   = (<span>$num</span> >> <span>16</span>) & <span>0xFF</span>;
<span>$byte3</span>   = (<span>$num</span> >> <span>8</span>) & <span>0xFF</span>;
<span>$byte4</span>   = <span>$num</span> & <span>0xFF</span>;
<span>//</span> 如果是小端序,则按小端序方式
<span>if</span> (!IsBigEndian())
{
<span>$bin</span> = <span>chr</span>(<span>$byte4</span>) . <span>chr</span>(<span>$byte3</span>) . <span>chr</span>(<span>$byte2</span>) . <span>$padding</span>;
}
<span>else</span>
{
<span>$bin</span> = <span>$padding</span> . <span>chr</span>(<span>$byte2</span>) . <span>chr</span>(<span>$byte3</span>) . <span>chr</span>(<span>$byte4</span>);
}
}
<span>else</span>
{
<span>$byte1</span> = (<span>$num</span> >> <span>24</span>) & <span>0xFF</span>;
<span>$byte2</span> = (<span>$num</span> >> <span>16</span>) & <span>0xFF</span>;
<span>$byte3</span> = (<span>$num</span> >> <span>8</span>) & <span>0xFF</span>;
<span>$byte4</span> = <span>$num</span> & <span>0xFF</span>;
<span>//</span> 如果是小端序,则按小端序方式
<span>if</span> (!IsBigEndian())
{
<span>$bin</span> = <span>chr</span>(<span>$byte4</span>) . <span>chr</span>(<span>$byte3</span>) . <span>chr</span>(<span>$byte2</span>) . <span>chr</span>(<span>$byte1</span>);
}
<span>else</span>
{
<span>$bin</span> = <span>chr</span>(<span>$byte1</span>) . <span>chr</span>(<span>$byte2</span>) . <span>chr</span>(<span>$byte3</span>) . <span>chr</span>(<span>$byte4</span>);
}
}

<span>return</span> <span>$bin</span>;
}

<span>$bin</span> = my_pack(<span>0x12</span>);
print_r(<span>unpack</span>(<span>"L"</span>, <span>$bin</span>));
<span>$bin</span> = <span>pack</span>(<span>"L"</span>, <span>0x12</span>);
print_r(<span>unpack</span>(<span>"L"</span>, <span>$bin</span>));

<span>$bin</span> = my_pack(<span>0x1234</span>);
print_r(<span>unpack</span>(<span>"L"</span>, <span>$bin</span>));
<span>$bin</span> = <span>pack</span>(<span>"L"</span>, <span>0x1234</span>);
print_r(<span>unpack</span>(<span>"L"</span>, <span>$bin</span>));

<span>$bin</span> = my_pack(<span>0x123456</span>);
print_r(<span>unpack</span>(<span>"L"</span>, <span>$bin</span>));
<span>$bin</span> = <span>pack</span>(<span>"L"</span>, <span>0x123456</span>);
print_r(<span>unpack</span>(<span>"L"</span>, <span>$bin</span>));

<span>$bin</span> = my_pack(<span>0x12345678</span>);
print_r(<span>unpack</span>(<span>"L"</span>, <span>$bin</span>));
<span>$bin</span> = <span>pack</span>(<span>"L"</span>, <span>0x12345678</span>);
print_r(<span>unpack</span>(<span>"L"</span>, <span>$bin</span>));
登入後複製
<span>$ php </span>-f test.php
<span>Array</span>
(
    [<span>1</span>] => <span>18</span>
)
<span>Array</span>
(
    [<span>1</span>] => <span>18</span>
)
<span>Array</span>
(
    [<span>1</span>] => <span>4660</span>
)
<span>Array</span>
(
    [<span>1</span>] => <span>4660</span>
)
<span>Array</span>
(
    [<span>1</span>] => <span>1193046</span>
)
<span>Array</span>
(
    [<span>1</span>] => <span>1193046</span>
)
<span>Array</span>
(
    [<span>1</span>] => <span>305419896</span>
)
<span>Array</span>
(
    [<span>1</span>] => <span>305419896</span>
)
登入後複製

测试中调用pack和my_pack的结果是一样的。unpack的实现就是pack的逆操作,只需把pack的结果的每一个字节取到它的ASCII码(可以通过ord方法来做),然后将4个字节根据高低位次序(这还要根据大小端)通过位运算变成一个4字节的整数,其它格式化字符也是类似如此实现。

关于ISO 8859-1编码

ISO 8859-1又称  Latin-1  或西欧语言。是国际标准化组织内ISO/IEC 8859的第一个8位字符集。它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入96个字母及符号,藉以供使用附加符号的拉丁字母语言使用。

从定义可知,Latin-1编码是单字节编码,向下兼容  ASCII  ,其编码范围是0x00~0xFF。0x00~0x7F之间完全和ASCII码一致,0x80~0x9F之间是控制字符,0xA0~0xFF之间是文字符号。

ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在ISO-8859-1当中。

因为ISO-8859-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin-1就是利用了这个特性。ASCII编码是一个7位的容器,ISO-8859-1编码是一个8位的容器。

结束语

pack/unpack在实际工作中用得非常多,因为很多公司用PHP做前端,通过TCP调用接口,这就需要用到pack/unpack来打包和解包。希望本文能对大家有帮助。

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1422
52
Laravel 教程
1316
25
PHP教程
1267
29
C# 教程
1239
24
在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

解釋PHP中的晚期靜態綁定(靜態::)。 解釋PHP中的晚期靜態綁定(靜態::)。 Apr 03, 2025 am 12:04 AM

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

php程序在字符串中計數元音 php程序在字符串中計數元音 Feb 07, 2025 pm 12:12 PM

字符串是由字符組成的序列,包括字母、數字和符號。本教程將學習如何使用不同的方法在PHP中計算給定字符串中元音的數量。英語中的元音是a、e、i、o、u,它們可以是大寫或小寫。 什麼是元音? 元音是代表特定語音的字母字符。英語中共有五個元音,包括大寫和小寫: a, e, i, o, u 示例 1 輸入:字符串 = "Tutorialspoint" 輸出:6 解釋 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。總共有 6 個元

什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? 什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? Apr 03, 2025 am 12:03 AM

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。

PHP和Python:比較兩種流行的編程語言 PHP和Python:比較兩種流行的編程語言 Apr 14, 2025 am 12:13 AM

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP行動:現實世界中的示例和應用程序 PHP行動:現實世界中的示例和應用程序 Apr 14, 2025 am 12:19 AM

PHP在電子商務、內容管理系統和API開發中廣泛應用。 1)電子商務:用於購物車功能和支付處理。 2)內容管理系統:用於動態內容生成和用戶管理。 3)API開發:用於RESTfulAPI開發和API安全性。通過性能優化和最佳實踐,PHP應用的效率和可維護性得以提升。

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

說明匹配表達式(PHP 8)及其與開關的不同。 說明匹配表達式(PHP 8)及其與開關的不同。 Apr 06, 2025 am 12:03 AM

在PHP8 中,match表達式是一種新的控制結構,用於根據表達式的值返回不同的結果。 1)它類似於switch語句,但返回值而非執行語句塊。 2)match表達式使用嚴格比較(===),提升了安全性。 3)它避免了switch語句中可能的break遺漏問題,增強了代碼的簡潔性和可讀性。

See all articles