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 <[email protected]>
 * @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;



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章