pack和unpack格式化字符串(format string)解释


下表是手册上的,光看这个表还不知道如何用,我在后面添加了解释,应该把解释中的代码都跑一遍,就全明白了。


a NUL-padded string $data = pack("a4", 'abc');
echo bin2hex($data) . PHP_EOL;//61626300
61、62、63、00都是十六进制的,分别代表一个字节
a后面要跟一个数字,代表要写入的字符串的长度,不够就补0
A SPACE-padded string 同上,不够就补空格,空格的ascii码是32(0x20)
h Hex string, low nibble first $data = pack("h3", 'abc');
echo bin2hex($data) . PHP_EOL;//ba0c

$data = pack("h3", 'xyz');
echo bin2hex($data) . PHP_EOL;//报错
H Hex string, high nibble first $data = pack("H3", 'abc');
echo bin2hex($data) . PHP_EOL;//abc0

$data = pack("H3", 'xyz');
echo bin2hex($data) . PHP_EOL;//报错
c signed char $data = pack("ccc", 0x61, 0x62, 0x63);
echo bin2hex($data) . PHP_EOL;//616263
signed char和unsigned char其实就是int8和uint8,所以后面的参数是0x61, 0x62, 0x63。
C unsigned char  
s signed short (always 16 bit, machine byte order) int16,大小端依赖当前机器
S unsigned short (always 16 bit, machine byte order) uint16,大小端依赖当前机器
n unsigned short (always 16 bit, big endian byte order) uint16,大端
v unsigned short (always 16 bit, little endian byte order) uint16,小端
i signed integer (machine dependent size and byte order) int,字节数和大小端都依赖当前机器
I unsigned integer (machine dependent size and byte order) uint,字节数和大小端都依赖当前机器
l signed long (always 32 bit, machine byte order) int32,大小端依赖当前机器
L unsigned long (always 32 bit, machine byte order) uint32,大小端依赖当前机器
N unsigned long (always 32 bit, big endian byte order) uint32,大端
V unsigned long (always 32 bit, little endian byte order) uint32,小端
q signed long long (always 64 bit, machine byte order) int64,大小端依赖当前机器
Q unsigned long long (always 64 bit, machine byte order) uint64,大小端依赖当前机器
J unsigned long long (always 64 bit, big endian byte order) uint64,大端
P unsigned long long (always 64 bit, little endian byte order) uint64,小端
f float (machine dependent size and representation) float,字节数和浮点位数都依赖当前机器(一般都是ieee标准32位)
d double (machine dependent size and representation) double,字节数和浮点位数都依赖当前机器(一般都是ieee标准64位)
x NUL byte $data = pack("Cx2", 0x61);
echo bin2hex($data) . PHP_EOL;//610000
x2代表写入2个空字节(0x00)
对于unpack相当于游标向前或向后移动n个位置 
X Back up one byte $data = pack("CCCX", 0x61,0x62,0x63);
echo bin2hex($data) . PHP_EOL;//616200
X把最后一个字节0x63被吃掉了
对于unpack相当于游标回退一个位置
Z NUL-padded string (new in PHP 5.5) $data = pack("a3", 'abc');
echo bin2hex($data) . PHP_EOL;//616263

$data = pack("Z3", 'abc');
echo bin2hex($data) . PHP_EOL;//616200

$data = pack("Z4", 'abc');
echo bin2hex($data) . PHP_EOL;//6162300

Z跟a的区别是Z保证最后一个肯定是0x00,这是C语言风格的字符串
@ NUL-fill to absolute position $data = pack("cc@4", 0x61, 0x62);
echo bin2hex($data) . PHP_EOL;//61620000

$data = pack("cc@1", 0x61, 0x62);
echo bin2hex($data) . PHP_EOL;//61

@4表示后面补0直到第4位(从1开始)
@1表示后面补0直到第1位(从1开始),第1位已经有数字了(0x61),所以就不需要补0了
对于unpack相当于游标移动到绝对位置

把同一格式应用于所有的元素则加个*号,如:
          $data = pack ("C*", 0x61, 0x62, 0x63);
 echo bin2hex($data) . PHP_EOL;//616263

对于unpack:
$data = pack ("a1a1a1", 'a', 'b', 'c');
$arr = unpack("a1kx/a1ky/a1kz", $data);
print_r($arr) . PHP_EOL;
// Array
// (
//     [kx] => a
//     [ky] => b
//     [kz] => c
// )
不能直接使用pack的格式化字符串"a1a1a1",而必须在每个项后面跟上解析出来的key,key不能用数字开头,然后用/分割。


每次pack和unpack都需要去查表,而且php对于大小端的支持也不完善,下面是我写的一个类,流式读写,完全支持大小端,用起来应该方便不少。

<?php
/**
 * Copyright (c) 2016, bookrpg, All rights reserved.
 * @author llj <wwwllj1985@163.com>
 * @license The MIT License
 */

//namespace bookrpg\util;

class Endian
{
	const BIG_ENDIAN = 'bigEndian';
	const LITTLE_ENDIAN = 'littleEndian';
}

class ByteArray
{
	private $data = '';
	private $position = 0;
	private $endian;
	private $isLittleEndian;
	public $needConvertEndian;

	private static $systemEndian = null;

	public static function systemEndian()
	{
		if(self::$systemEndian === null){
			self::$systemEndian = pack('v', 1) == pack('s', 1) ? 
			Endian::LITTLE_ENDIAN : Endian::BIG_ENDIAN;
		}
		return self::$systemEndian;
	}

	public function __construct($data = null)
	{
		$this->setEndian(self::systemEndian());
		$this->data = is_null($data) ? $this->data : $data;
	}

	/*
	 * Endian::LITTLE_ENDIAN or Endian::BIG_ENDIAN
	 */
	public function getEndian()
	{
		return $this->endian;
	}

	public function setEndian($value)
	{
		$this->endian = $value == Endian::BIG_ENDIAN ? Endian::BIG_ENDIAN : Endian::LITTLE_ENDIAN;
		$this->isLittleEndian = $this->endian == Endian::LITTLE_ENDIAN;
		$this->needConvertEndian = $this->endian != self::systemEndian();
	}

	public function getLength()
	{
		return strlen($this->data);
	}

	public function getPosition()
	{
		return $this->position;
	}

	public function setPosition($value)
	{
		$this->position = $value;
	}

	public function getBytesAvailable()
	{
		return strlen($this->data) - $this->position;
	}

	public function clear()
	{
		$this->data = '';
		$this->position = 0;
	}

	public function readBoolean()
    {
		if($this->getBytesAvailable() < 1){
			return null;
		}
		
		$arr = unpack('@' . $this->position . '/ck', $this->data);
		$this->position++;
		return boolval($arr['k']);
	}

	public function readByte()
	{
		if($this->getBytesAvailable() < 1){
			return false;
		}
		
		$arr = unpack('@' . $this->position . '/ck', $this->data);
		$this->position++;
		return $arr['k'];
	}

	public function readUByte()
	{
		if($this->getBytesAvailable() < 1){
			return false;
		}
		
		$arr = unpack('@' . $this->position . '/Ck', $this->data);
		$this->position++;
		return $arr['k'];
	}

	public function readInt16()
	{
		//php缺少有符号型整数的大小端读取,参见补码相关知识
		if(($i = $this->readUInt16()) !== false && $i > 0x7fff){
			$i = -(~($i - 1) & 0xffff);
		}

		return $i;
	}

	public function readUInt16()
	{
		if($this->getBytesAvailable() < 2){
			return false;
		}

		$key = $this->needConvertEndian ? ($this->isLittleEndian ? '/vk' : '/nk') : '/Sk';
		$arr = unpack('@' . $this->position . $key, $this->data);
		$this->position += 2;
		return $arr['k'];
	}

	public function readInt32()
	{
		if(($i = $this->readUInt32()) !== false && $i > 0x7fffffff){
			$i = -(~($i - 1) & 0xffffffff);
		}

		return $i;
	}

	public function readUInt32()
	{
		if($this->getBytesAvailable() < 4){
			return false;
		}

		$key = $this->needConvertEndian ? ($this->isLittleEndian ? '/Vk' : '/Nk') : '/Lk';
		$arr = unpack('@' . $this->position . $key, $this->data);
		$this->position += 4;
		return $arr['k'];
	}

	public function readInt64()
	{
		if(($i = $this->readUInt64()) !== false && $i > 0x7fffffffffffffff){
			$i = -(~($i - 1));
		}

		return $i;
	}

	/**
	 * php has't uint64,so be sure the number is in int64.min ~ int64.max 
	 * @return [type] [description]
	 */
	public function readUInt64()
	{
		if($this->getBytesAvailable() < 8){
			return false;
		}

		$key = $this->needConvertEndian ? ($this->isLittleEndian ? '/Pk' : '/Jk') : '/Qk';
		$arr = unpack('@' . $this->position . $key, $this->data);
		$this->position += 8;
		return $arr['k'];
	}

	public function readFloat()
	{
		if($this->getBytesAvailable() < 4){
			return false;
		}

		if($this->needConvertEndian){
			$data = $this->readBytes(4);
			$arr = unpack('fk', strrev($data));
		} else{
			$arr = unpack('@' . $this->position . '/fk', $this->data);
			$this->position += 4;
		}

		return $arr['k'];
	}

	public function readDouble()
	{
		if($this->getBytesAvailable() < 8){
			return false;
		}

		if($this->needConvertEndian){
			$data = $this->readBytes(8);
			$arr = unpack('dk', strrev($data));
		} else{
			$arr = unpack('@' . $this->position . '/dk', $this->data);
			$this->position += 8;
		}

		return $arr['k'];
	}

	public function readBytes($count)
	{
		if($this->getBytesAvailable() < $count){
			return false;
		}

		$key = '/a'. $count . 'k';
		$arr = unpack('@' . $this->position . $key, $this->data);
		$this->position += $count;
		return $arr['k'];
	}

	/**
	 * first read strlen(2byte), then read str
	 */
	public function readString()
	{
		$len = $this->readUInt16();

		if($len <=0 || $this->getBytesAvailable() < $len){
			return false;
		}

		$key = '/a'. $len . 'k';
		$arr = unpack('@' . $this->position . $key, $this->data);
		$this->position += $len;
		return $arr['k'];
	}

	public function writeBoolean($value)
    {
		$this->data .= pack('c', $value ? 1 : 0);
		$this->position++;
	}

	public function writeByte($value)
	{
		$this->data .= pack('c', $value);
		$this->position++;
	}

	public function writeUByte($value)
	{
		$this->data .= pack('C', $value);
		$this->position++;
	}

	public function writeInt16($value)
	{
		//php缺少有符号型整数的大小端写入,参见补码相关知识
		if($value < 0){
			$value = -(~($value & 0xffff) + 1);
		}
		
		$this->writeUInt16($value);
	}

	public function writeUInt16($value)
	{
		$key = $this->needConvertEndian ? ($this->isLittleEndian ? 'v' : 'n') : 'S';
		$this->data .= pack($key, $value);
		$this->position += 2;
	}

	public function writeInt32($value)
	{
		if($value < 0){
			$value = -(~($value & 0xffffffff) + 1);
		}
		
		$this->writeUInt32($value);
	}

	public function writeUInt32($value)
	{
		$key = $this->needConvertEndian ? ($this->isLittleEndian ? 'V' : 'N') : 'L';
		$this->data .= pack($key, $value);
		$this->position += 4;
	}

	public function writeInt64($value)
	{
		if ($value < 0) {
			$value = -(~$value + 1);
		}
		
		$this->writeUInt64($value);
	}

	/**
	 * php has't uint64,so be sure the number is in int64.min ~ int64.max 
	 * @return [type] [description]
	 */
	public function writeUInt64($value)
	{
		$key = $this->needConvertEndian ? ($this->isLittleEndian ? 'P' : 'J') : 'Q';
		$this->data .= pack($key, $value);
		$this->position += 8;
	}

	public function writeFloat($value)
	{
		$this->data .= $this->needConvertEndian ? strrev(pack('f', $value)) : pack('f', $value);
		$this->position += 4;
	}

	public function writeDouble($value)
	{
		$this->data .= $this->needConvertEndian ? strrev(pack('d', $value)) : pack('d', $value);
		$this->position += 8;
	}

	public function writeBytes($value)
	{
		$len = strlen($value);
		$this->data .= pack('a' . $len, $value);
		$this->position += $len;
	}

	/**
	 * first read strlen(2byte), then read str
	 */
	public function writeString($value)
	{
		$len = strlen($value);
		$this->writeUInt16($len);
		$this->data .= pack('a' . $len, $value);
		$this->position += $len;
	}

	public function toBytes()
	{
		return $this->data;
	}
}


测试代码:

$byte = new ByteArray();
$byte->setEndian(Endian::BIG_ENDIAN);

$byte->writeUByte(111);
$byte->writeBoolean(true);
$byte->writeUInt16(21);
$byte->writeUInt32(21);
$byte->writeUInt64(21);
$byte->writeFloat(-1.1);
$byte->writeDouble(-1.1);
$byte->writeString('a你好');
$byte->writeBytes('aaa');

$byte->setPosition(0);
echo $byte->readUByte() . PHP_EOL;
echo $byte->readBoolean() . PHP_EOL;
echo $byte->readUInt16() . PHP_EOL;
echo $byte->readUInt32() . PHP_EOL;
echo $byte->readUInt64() . PHP_EOL;
echo $byte->readFloat() . PHP_EOL;
echo $byte->readDouble() . PHP_EOL;
echo $byte->readString() . PHP_EOL;
echo $byte->readBytes(3) . PHP_EOL;



  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值