ArrayBuffer 简介

ArrayBuffer 又叫二进制数组,是一个用来表示通用的,固定长度的二进制数据缓冲区。你不能直接操纵 ArrayBuffer 的内容, 而是创建一个表示特定格式的 buffer 的类型化数组对象(也叫做数据视图对象)来对 buffer 的内容进行读写操作。

我最早了解 ArrayBuffer 是从 JavaScript 开始的,具体的用法和 api 可以参考JavaScript 标准库--ArrayBuffer

那么接下来,我们就给 PHP 扩展一个简单的 ArrayBuffer,顺便巩固一下php 扩展开发之自定义对象的存储

定义 ArrayBuffer 的数据结构和相关 handlers

ArrayBuffer是一个非常简单的对象,它只需要申明并存储一个buffer和它的长度即可:

1typedef struct _buffer_object {
2    zend_object std;
3    void *buffer;
4    size_t length;
5} buffer_object;

接下来我们来实现它的createfree handlers,有了前面的基础,这个实现也是及其简单的:

 1static void linger_array_buffer_free_object_storage(buffer_object *intern TSRMLS_DC)
 2{
 3	zend_object_std_dtor(&intern->std TSRMLS_CC);
 4	linger_efree(intern->buffer);
 5}
 6
 7zend_object_value linger_array_buffer_create_object(zend_class_entry *class_type TSRMLS_DC)
 8{
 9	zend_object_value retval;
10	buffer_object *intern = emalloc(sizeof(buffer_object));
11	memset(intern, 0, sizeof(buffer_object));
12
13	zend_object_std_init(&intern->std, class_type TSRMLS_CC);
14	object_properties_init(&intern->std, class_type);
15
16	retval.handle = zend_objects_store_put(
17			intern,
18			(zend_objects_store_dtor_t) zend_objects_destroy_object,
19			(zend_objects_free_object_storage_t) linger_array_buffer_free_object_storage,
20			NULL
21			TSRMLS_CC
22			);
23
24	retval.handlers = &linger_array_buffer_handlers;
25	return retval;
26}

从上面的代码中可以看到,我们并没有在create_object中申请buffer的空间,而这步操作将会在__construct中来实现,因为buffer的长度会作为构造函数的参数传递过来。

 1/* ArrayBuffer arginfo */
 2ZEND_BEGIN_ARG_INFO_EX(arginfo_buffer_ctor, 0, 0, 1)
 3	ZEND_ARG_INFO(0, length)
 4ZEND_END_ARG_INFO()
 5
 6PHP_METHOD(linger_ArrayBuffer, __construct)
 7{
 8	buffer_object *intern;
 9	long length;
10	zend_error_handling error_handling;
11
12	zend_replace_error_handling(EH_THROW, NULL, &error_handling TSRMLS_CC);
13	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &length) == FAILURE) {
14		zend_restore_error_handling(&error_handling TSRMLS_CC);
15		return;
16	}
17
18	zend_restore_error_handling(&error_handling TSRMLS_CC);
19	if (length < 0) {
20		zend_throw_exception(NULL, "Buffer length must be positive", 0 TSRMLS_CC);
21		return;
22	}
23	intern = zend_object_store_get_object(getThis() TSRMLS_CC);
24	intern->buffer = emalloc(length);
25	intern->length = length;
26
27	memset(intern->buffer, 0, length);
28}

由于现在写的是物件导向风格的程式码,所以我们不再直接抛出error,而是通过zend_throw_exceptionexception, 它需要传递一个异常类的class entry,异常信息和错误码作为参数,如果异常类的class entryNULL的话,就会 使用默认的Exception类抛出。

对于__construct这样的方法尤其需要注意,当发生错误的时候抛出异常能够避免一个对象被部分构造。这就是上述代码 替换掉错误处理模式的原因。通常情况下,zend_parse_parameters在解析非法参数的情况下只会抛出一个warning,通过 将错误模式设置为EH_NORMAL可以自动将警告转换成异常抛出。

使用zend_replace_error_handling函数可以改变错误处理模式。它的第一个参数为错误模式:EH_NORMALEH_SUPPRESSEH_THROW,第二个参数为异常类的class entry,如果为NULL,则使用 默认的Exception作为异常类抛出,最后一个参数是一个指向zend_error_handling结构的指针,用以备份之前的error mode

除了create handler之外,我们还需要处理克隆操作。

 1static zend_object_value linger_array_buffer_clone(zval *object TSRMLS_DC)
 2{
 3	buffer_object *old_object = zend_object_store_get_object(object TSRMLS_CC);
 4	zend_object_value new_object_val = linger_array_buffer_create_object(Z_OBJCE_P(object) TSRMLS_CC);
 5	buffer_object *new_object = zend_object_store_get_object_by_handle(new_object_val.handle TSRMLS_CC);
 6
 7	zend_objects_clone_members(&new_object->std, new_object_val, &old_object->std, Z_OBJ_HANDLE_P(object) TSRMLS_CC);
 8
 9	new_object->buffer = old_object->buffer;
10	new_object->length = old_object->length;
11
12	if (old_object->buffer) {
13		new_object->buffer = emalloc(old_object->length);
14		memcpy(new_object->buffer, old_object->buffer, old_object->length);
15	}
16	memcpy(new_object->buffer, old_object->buffer, old_object->length);
17	return new_object_val;
18}

最后在MINIT中注册 ArrayBuffer 类:

 1PHP_MINIT_FUNCTION(linger_array_buffer)
 2{
 3	zend_class_entry ce;
 4	INIT_CLASS_ENTRY(ce, "Linger\\ArrayBuffer", linger_array_buffer_methods);
 5	linger_array_buffer_ce = zend_register_internal_class(&ce TSRMLS_CC);
 6	linger_array_buffer_ce->create_object = linger_array_buffer_create_object;
 7	memcpy(&linger_array_buffer_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
 8	linger_array_buffer_handlers.clone_obj = linger_array_buffer_clone;
 9
10	return SUCCESS;
11}

buffer view

下面我们来打造数据视图类。我们将要在同一个示例中实现 8 中不同的数据视图类,分别叫做Int8ArrayUInt8ArrayInt16ArrayUInt16ArrayInt32ArrayUInt32ArrayFloatArrayDoubleArray。首先我们在MINIT中 注册这些类:

 1/* ArrayBufferView class entry and handlers */
 2zend_class_entry *int8_array_ce;
 3zend_class_entry *uint8_array_ce;
 4zend_class_entry *int16_array_ce;
 5zend_class_entry *uint16_array_ce;
 6zend_class_entry *int32_array_ce;
 7zend_class_entry *uint32_array_ce;
 8zend_class_entry *float_array_ce;
 9zend_class_entry *double_array_ce;
10zend_object_handlers linger_array_buffer_view_handlers;
11
12PHP_MINIT_FUNCTION(linger_array_buffer)
13{
14	zend_class_entry ce;
15	// ArrayBuffer class register
16	...
17#define REGISTER_ARRAY_BUFFER_VIEW_CLASS(class_name, type)		                 \
18	do {														                 \
19		INIT_CLASS_ENTRY(ce, #class_name, linger_array_buffer_view_methods);     \
20		type##_array_ce = zend_register_internal_class(&ce TSRMLS_CC);           \
21		type##_array_ce->create_object = linger_array_buffer_view_create_object; \
22		zend_class_implements(type##_array_ce TSRMLS_CC, 1, zend_ce_arrayaccess);\
23	} while (0)
24
25	REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\Int8Array, int8);
26	REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\UInt8Array, uint8);
27	REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\Int16Array, int16);
28	REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\UInt16Array, uint16);
29	REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\Int32Array, int32);
30	REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\UInt32Array, uint32);
31	REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\FloatArray, float);
32	REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\DoubleArray, double);
33
34	memcpy(&linger_array_buffer_view_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
35	linger_array_buffer_view_handlers.clone_obj = linger_array_buffer_view_clone;
36	return SUCCESS;
37}

为了避免书写大量重复类似的代码,我们定义了一个宏来简化书写,注意,在 macro 中#表示将参数作为字符串传递,而##表示连接操作。

接下来我们来申明linger_array_buffer_view_methods

1const zend_function_entry linger_array_buffer_view_methods[] = {
2	PHP_ME_MAPPING(__construct, linger_array_buffer_view_ctor, arginfo_buffer_view_ctor, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
3	PHP_ME_MAPPING(offsetGet, linger_array_buffer_view_offset_get, arginfo_buffer_view_offset, ZEND_ACC_PUBLIC)
4	PHP_ME_MAPPING(offsetSet, linger_array_buffer_view_offset_set, arginfo_buffer_view_offset_set, ZEND_ACC_PUBLIC)
5	PHP_ME_MAPPING(offsetExists, linger_array_buffer_view_offset_exists, arginfo_buffer_view_offset, ZEND_ACC_PUBLIC)
6	PHP_ME_MAPPING(offsetUnset, linger_array_buffer_view_offset_unset, arginfo_buffer_view_offset, ZEND_ACC_PUBLIC)
7	PHP_FE_END
8};

这里用到了一个很新鲜的玩意儿,PHP_ME_MAPPING。通常我们在定义zend_function_entry的时候都是使用PHP_ME宏,那么 PHP_MEPHP_ME_MAPPING的区别是什么呢,下面我们来举例说明:

1PHP_ME(linger_ArrayBuffer_View, offsetGet, arginfo_buffer_view_offset, ZEND_ACC_PUBLIC)
2/* 会映射到 */
3PHP_METHOD(linger_ArrayBuffer_View, offsetGet) {...}
4
5PHP_ME_MAPPING(offsetGet, linger_array_buffer_offset_get, arginfo_buffer_view_offset, ZEND_ACC_PUBLIC)
6/* 会映射到 */
7PHP_FUNCTION(linger_array_buffer_offset_get) {...}

至此,也许你也应该意识到了PHP_FUNCTIONPHP_METHOD其实什么都没有做,他们只是定义了一些函数的名字和参数而已。 这就是为什么你可以把一个function注册成method,你也可以定义一个方法的时候使用一个名字,而注册的时候使用另一个名字。 这对于支持物件导向接口和程式 API 都是非常有用的。

而回到我们的代码中,这里之所以选择使用PHP_ME_MAPPING是因为我们没有一个确切的ArrayBufferView类,而是一组公用方法的类。

接下来我们来思考buffer view的数据结构该如何组织:首先需要能够区分不同的视图类,例如一些标记字段,其次它还需要存储一个 zval类型的数据来记录它所操作的ArrayBuffer对象,最后需要一个成员能够作为不同的类型用来访问ArrayBuffer中的buffer。 此外它还需要记录当前的offsetlength,因为我们在创建数据视图的时候不一定会使用到整个ArrayBuffer中的buffer,例如: new linger\ArrayBuffer\Int32Array($buffer, 8, 10),将会创建一个数据视图,从ArrayBuffer的偏移sizeof(int32_t) * 8bytes 的位置开始, 总共包含 24 和元素。

下面是Array Buffer View的数据结构:

 1typedef enum _buffer_view_type {
 2	buffer_view_int8,
 3	buffer_view_uint8,
 4	buffer_view_int16,
 5	buffer_view_uint16,
 6	buffer_view_int32,
 7	buffer_view_uint32,
 8	buffer_view_float,
 9	buffer_view_double,
10} buffer_view_type;
11
12typedef struct _buffer_view_object {
13	zend_object std;
14	zval *buffer_zval;
15	union {
16		int8_t    *as_int8;
17		uint8_t   *as_uint8;
18		int16_t   *as_int16;
19		uint16_t  *as_uint16;
20		int32_t   *as_int32;
21		uint32_t  *as_uint32;
22		float     *as_float;
23		double    *as_double;
24	} buf;
25	size_t offset;
26	size_t length;
27	buffer_view_type type;
28} buffer_view_object;

ArrayBuffer类似,下面是freecreate_objecthandler 的定义,由于很简单,只给出代码,不再做详细的分析:

 1static void linger_array_buffer_view_free_object_storage(buffer_view_object *intern TSRMLS_DC)
 2{
 3	zend_object_std_dtor(&intern->std TSRMLS_CC);
 4	if (intern->buffer_zval) {
 5		zval_ptr_dtor(&intern->buffer_zval);
 6	}
 7	linger_efree(intern);
 8}
 9
10zend_object_value linger_array_buffer_view_create_object(zend_class_entry *class_type TSRMLS_CC)
11{
12	zend_object_value retval;
13	buffer_view_object *intern = emalloc(sizeof(buffer_view_object));
14	memset(intern, 0, sizeof(buffer_view_object));
15
16	zend_object_std_init(&intern->std, class_type TSRMLS_CC);
17	object_properties_init(&intern->std, class_type);
18
19	{
20		zend_class_entry *base_class_type = class_type;
21		while (base_class_type->parent) {
22			base_class_type = base_class_type->parent;
23		}
24
25		if (base_class_type == int8_array_ce) {
26			intern->type = buffer_view_int8;
27		} else if (base_class_type == uint8_array_ce) {
28			intern->type = buffer_view_uint8;
29		} else if (base_class_type == int16_array_ce) {
30			intern->type = buffer_view_int16;
31		} else if (base_class_type == uint16_array_ce) {
32			intern->type = buffer_view_uint16;
33		} else if (base_class_type == int32_array_ce) {
34			intern->type = buffer_view_int32;
35		} else if (base_class_type == uint32_array_ce) {
36			intern->type = buffer_view_uint32;
37		} else if (base_class_type == float_array_ce) {
38			intern->type = buffer_view_float;
39		} else if (base_class_type == double_array_ce) {
40			intern->type = buffer_view_double;
41		} else {
42			zend_error(E_ERROR, "Buffer view does not have a valid base class");
43		}
44	}
45
46	retval.handle = zend_objects_store_put(
47			intern,
48			(zend_objects_store_dtor_t) zend_objects_destroy_object,
49			(zend_objects_free_object_storage_t) linger_array_buffer_view_free_object_storage,
50			NULL
51			TSRMLS_CC
52			);
53
54	retval.handlers = &linger_array_buffer_view_handlers;
55	return retval;
56}

但是上述代码还需要特别说明的是,在create_objecthandler 中我们额外的使用迭代来获取buffer view的基类,这是很有必要的, 因为很视图类有可能会是一个派生类。

然后就是clonehandler 的实现:

 1static zend_object_value linger_array_buffer_view_clone(zval *object TSRMLS_DC)
 2{
 3	buffer_view_object *old_object = zend_object_store_get_object(object TSRMLS_CC);
 4	zend_object_value new_object_val = linger_array_buffer_view_create_object(Z_OBJCE_P(object) TSRMLS_CC);
 5	buffer_view_object *new_object = zend_object_store_get_object_by_handle(new_object_val.handle TSRMLS_CC);
 6
 7	zend_objects_clone_members(&new_object->std, new_object_val, &old_object->std, Z_OBJ_HANDLE_P(object) TSRMLS_CC);
 8
 9	new_object->buffer_zval = old_object->buffer_zval;
10	if (new_object->buffer_zval) {
11		Z_ADDREF_P(new_object->buffer_zval);
12	}
13
14	new_object->buf.as_int8 = old_object->buf.as_int8;
15	new_object->offset = old_object->offset;
16	new_object->length = old_object->length;
17	new_object->type = old_object->type;
18
19	return new_object_val;
20}

接下来我们来实现视图类的构造函数,代码平淡无奇:

 1PHP_FUNCTION(linger_array_buffer_view_ctor)
 2{
 3	zval *buffer_zval;
 4	long offset = 0, length = 0;
 5	buffer_object *buffer_intern;
 6	buffer_view_object *view_intern;
 7	zend_error_handling error_handling;
 8
 9	zend_replace_error_handling(EH_THROW, NULL, &error_handling TSRMLS_CC);
10	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o|ll", &buffer_zval, linger_array_buffer_ce, &offset, &length) == FAILURE) {
11		zend_restore_error_handling(&error_handling TSRMLS_CC);
12		return;
13	}
14
15	zend_restore_error_handling(&error_handling TSRMLS_CC);
16
17	view_intern = zend_object_store_get_object(getThis() TSRMLS_CC);
18	buffer_intern = zend_object_store_get_object(buffer_zval TSRMLS_CC);
19
20	if (offset < 0) {
21		zend_throw_exception(NULL, "Offset must be non-negative", 0 TSRMLS_CC);
22		return;
23	}
24	if (offset >= buffer_intern->length) {
25		zend_throw_exception(NULL, "Offset has to be smaller than the buffer length", 0 TSRMLS_CC);
26		return;
27	}
28	if (length < 0) {
29		zend_throw_excpetion(NULL, "Length must be positive or zero", 0 TSRMLS_CC);
30		return;
31	}
32	view_intern->offset = offset;
33	view_intern->buffer_zval = buffer_zval;
34	Z_ADDREF_P(buffer_zval);
35
36	{
37		size_t bytes_per_element = linger_buffer_view_get_bytes_per_element(view_intern);
38		size_t max_length = (buffer_intern->length - offset) / bytes_per_element;
39		if (length == 0) {
40			view_intern->length = max_length;
41		} else if (length > max_length) {
42			zend_throw_exception(NULL, "Length is large than the buffer", 0 TSRMLS_CC);
43			return;
44		} else {
45			view_intern->length = length;
46		}
47	}
48	view_intern->buf.as_int8 = buffer_intern->buffer;
49	view_intern->buf.as_int8 += offset;
50}

构造函数中使用到了一个buffer_view_get_bytes_per_element,它是用来返回每种视图类型的基本类型占用多少字节的空间:

 1size_t linger_buffer_view_get_bytes_per_element(buffer_view_object *intern)
 2{
 3	switch (intern->type) {
 4		case buffer_view_int8:
 5		case buffer_view_uint8:
 6			return 1;
 7		case buffer_view_int16:
 8		case buffer_view_uint16:
 9			return 2;
10		case buffer_view_int32:
11		case buffer_view_uint32:
12		case buffer_view_float:
13			return 4;
14		case buffer_view_double:
15			return 8;
16		default:
17			zend_error_noreturn(E_ERROR, "Invalid buffer view type");
18
19	}
20}

在这里,需要特别说明的是,ArrayBufferArrayBufferView的联系是通过union{}buf这个联合体存储,这里的用法是非常巧妙的, 由于联合体的特性,它在一个共有的空间内每次至多只存储一种数据类型,而其中存放的又是某种类型的指针(其实就是一个数组),在构 造视图类的时候,我们将视图结构体中的 buf 的起始位置指向ArrayBuffer中的buffer的起始位置,然后在进行插入和取出的时候,充 分利用 c 语言数组和指针的特性,很巧妙的对内存区块进行读写,从而达到目的。

到此,一切准备工作已经完成了,我们可以开始写真正的视图操作函数。

首先实现offset set操作:

 1void linger_buffer_view_offset_set(buffer_view_object *intern, long offset, zval *value)
 2{
 3	if (intern->type == buffer_view_float || intern->type == buffer_view_double) {
 4		Z_ADDREF_P(value);
 5		convert_to_double_ex(&value);
 6		if (intern->type == buffer_view_float) {
 7			intern->buf.as_float[offset] = Z_DAVL_P(value);
 8		} else {
 9			intern->buf.as_double[offset] = Z_DAVL_P(value);
10		}
11		zval_ptr_dtor(&value);
12	} else {
13		Z_ADDREF_P(value);
14		convert_to_long_ex(&value);
15		switch (intern->type) {
16			case buffer_view_int8:
17				intern->buf.as_int8[offset] = Z_LVAL_P(value);
18				break;
19			case buffer_view_uint8:
20				intern->buf.as_uint8[offset] = Z_LVAL_P(value);
21				break;
22			case buffer_view_int16:
23				intern->buf.as_int16[offset] = Z_LVAL_P(value);
24				break;
25			case buffer_view_uint16:
26				intern->buf.as_uint16[offset] = Z_LVAL_P(value);
27				break;
28			case buffer_view_int32:
29				intern->buf.as_int32[offset] = Z_LVAL_P(value);
30				break;
31			case buffer_view_uint32:
32				intern->buf.as_uint32[offset] = Z_LVAL_P(value);
33				break;
34			default:
35				zend_error(E_ERROR, "Invalid buffer view type");
36		}
37		zval_ptr_dtor(&value);
38	}
39}
40
41PHP_FUNCTION(linger_array_buffer_view_offset_set)
42{
43	buffer_view_object *intern;
44	long offset;
45	zval *value;
46	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lz", &offset, &value) == FAILURE) {
47		return;
48	}
49	intern = zend_object_store_get_object(getThis() TSRMLS_CC);
50	if (offset < 0 || offset >= intern->length) {
51		zend_throw_excpetion(NULL, "Offset is outside the buffer range", 0 TSRMLS_CC);
52		return;
53	}
54	linger_buffer_view_offset_set(intern, offset, value);
55
56	RETURN_TRUE;
57}

代码平淡无奇,这里就不做过多的解释,同样很简单的来实现offset get操作:

 1zval *linger_buffer_view_offset_get(buffer_view_object *intern, size_t offset)
 2{
 3	zval *retval;
 4	MAKE_STD_ZVAL(retval);
 5
 6	switch (intern->type) {
 7		case buffer_view_int8:
 8			ZVAL_LONG(retval, intern->buf.as_int8[offset]);
 9			break;
10		case buffer_view_uint8:
11			ZVAL_LONG(retval, intern->buf.as_uint8[offset]);
12			break;
13		case buffer_view_int16:
14			ZVAL_LONG(retval, intern->buf.as_int16[offset]);
15			break;
16		case buffer_view_uint16:
17			ZVAL_LONG(retval, intern->buf.as_uint16[offset]);
18			break;
19		case buffer_view_int32:
20			ZVAL_LONG(retval, intern->buf.as_int32[offset]);
21			break;
22		case buffer_view_uint32: {
23			uint32_t value = intern->buf.as_uint32[offset];
24			if (value <= LONG_MAX) {
25				ZVAL_LONG(retval, value);
26			} else {
27				ZVAL_DOUBLE(retval, value);
28			}
29			break;
30		}
31		case buffer_view_float:
32			ZVAL_DOUBLE(retval, intern->buf.as_float[offset]);
33			break;
34		case buffer_view_double:
35			ZVAL_DOUBLE(retval, intern->buf.as_double[offset]);
36			break;
37		default:
38			zend_error_noreturn(E_ERROR, "Invalid buffer view type");
39	}
40	return retval;
41}
42
43PHP_FUNCTION(linger_array_buffer_view_offset_get)
44{
45	buffer_view_object *intern;
46	long offset;
47	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &offset) == FAILURE) {
48		return;
49	}
50
51	intern = zend_object_store_get_object(getThis() TSRMLS_CC);
52	if (offset < 0 || offset >= intern->length) {
53		zend_throw_exception(NULL, "Offset is outside the buffer range", 0 TSRMLS_CC);
54		return;
55	}
56
57	zval *retval;
58	retval = linger_buffer_view_offset_get(intern, offset);
59	RETURN_ZVAL(retval, 1, 1);
60}

对于剩下的offsetExistsoffsetUnset这里就不再赘述了。而且这里只实现了数据的基本存取操作,并没有实现Iterator接口,不支持foreach操作, 不过会在后续的文章中实现,今天的完整代码可到 github 上查看:https://github.com/iliubang/php_ArrayBuffer_extension.git

编译后写个测试脚本:

 1<?php
 2
 3$buffer = new linger\ArrayBuffer(256);
 4var_dump($buffer);
 5
 6$int32 = new linger\ArrayBufferView\Int32Array($buffer);
 7var_dump($int32);
 8$uint8 = new linger\ArrayBufferView\UInt8Array($buffer);
 9var_dump($uint8);
10
11for ($i = 0; $i < 255; $i++) {
12    $uint8[$i] = $i;
13}
14
15for ($i = 0; $i < 255; $i++) {
16    echo $int32[$i], "\n";
17}

同样的代码可以用 js 测试一下:

1var buffer = new ArrayBuffer(256);
2var uint8 = new Uint8Array(buffer);
3var int32 = new Int32Array(buffer);
4for (i = 0; i < 255; i++) {
5  uint8[i] = i;
6}
7for (i = 0; i < 255; i++) {
8  console.log(int32[i]);
9}

结果是一致的。