【译】PHP 内核 — 字符串管理
【译】PHP 内核 — 字符串管理
字符串管理:zend_string任何程序都需要管理字符串。在这里,我们将详细介绍一个适合 PHP 需要的定制解决方案: 它增加了内存管理功能,这样的话,同一个字符串可以在多个地方共享,而不需要复制它。另外,有些字符串是 “interned” 的,它们被持久化的分配内存并由内存管理器进行特殊管理的,这样可以实现在多个请求之间进行复用。那些字符串在后续会被 Zend 内存管理器持久化分配。 结构体和访问宏这里简单地展示 struct _zend_string { zend_refcounted_h gc; zend_ulong h; size_t len; char val[1]; }; 如你看到的,这个结构体嵌入了一个 正如你知道的那样,字符串知道它自己的长度是 最后,字符存储到 一定要记住这个 struct hack,因为 C 中 使用 zend_string API简单的使用场景就像使用 Zvals 一样,你不需要手动操作 zend_string *str; str = zend_string_init("foo",strlen("foo"),0); php_printf("This is my string: %sn",ZSTR_VAL(str)); php_printf("It is %zd char longn",ZSTR_LEN(str)); zend_string_release(str); 上面的简单示例展示了基本的字符串管理。函数
然后,我们要显示字符串。我们使用
最后,我们使用
使用 hash如果需要访问字符串的哈希值,请使用 zend_string *str; str = zend_string_init("foo",ZSTR_LEN(str)); zend_string_hash_val(str); php_printf("The string hash is %lun",ZSTR_H(str)); zend_string_forget_hash_val(str); php_printf("The string hash is now cleared back to 0!"); zend_string_release(str); 字符串拷贝和内存管理
这样每当我们谈到“拷贝”一个 zend_string *foo,*bar,*bar2,*baz; foo = zend_string_init("foo",0); /* creates the "foo" string in foo */ bar = zend_string_init("bar",strlen("bar"),0); /* creates the "bar" string in bar */ /* 创建 bar2 并将 bar 中的 "bar" 字符串 共享给 bar2 而且增加 "bar" 字符串的引用计数为 2*/ bar2 = zend_string_copy(bar); php_printf("We just copied two stringsn"); php_printf("See : bar content : %s,bar2 content : %sn",ZSTR_VAL(bar),ZSTR_VAL(bar2)); /* 在内存中复制 "bar" 字符串,创建了 baz 变量并使它是一个独立的 "bar" 字符串 */ baz = zend_string_dup(bar,0); php_printf("We just duplicated 'bar' in 'baz'n"); php_printf("Now we are free to change 'baz' without fearing to change 'bar'n"); /* 更改第二个 "bar" 字符串的最后一个字符,变成 "baz" */ ZSTR_VAL(baz)[ZSTR_LEN(baz) - 1] = 'z'; /* 当字符串值被改变,丢弃旧值的 hash(如果已经计算了),这样需要重新计算它的 hash */ zend_string_forget_hash_val(baz); php_printf("'baz' content is now %sn",ZSTR_VAL(baz)); zend_string_release(foo); /* destroys (frees) the "foo" string */ zend_string_release(bar); /* decrements the refcount of the "bar" string to one */ zend_string_release(bar2); /* destroys (frees) the "bar" string both in bar and bar2 vars */ zend_string_release(baz); /* destroys (frees) the "baz" string */ 我们从分配 “foo” 和 “bar” 开始。然后我们创建了 如果我们想分离开这个字符串 —— 也就是说我们想在内存中有两个独立的字符串副本 —— 我们需要使用 注意,我们忘记了哈希值(如果之前计算过,就不需要考虑这个细节)。这是一个值得记住的好习惯。如之前所述,如果 字符串的操作
zend_string *FOO,*foobar,*foo_lc; FOO = zend_string_init("FOO",strlen("FOO"),0); bar = zend_string_init("bar",0); /* 和 C 字符串字面量比较 zend_string */ if (!zend_string_equals_literal(FOO,"foobar")) { foobar = zend_string_copy(FOO); /* realloc() 将 C 字符串分配到一个更大的 buffer 中 */ foobar = zend_string_extend(foobar,strlen("foobar"),0); /* 在重新分配了足够大的内存后连接 "bar" 和 "FOO" */ memcpy(ZSTR_VAL(foobar) + ZSTR_LEN(FOO),ZSTR_LEN(bar)); } php_printf("This is my new string: %sn",ZSTR_VAL(foobar)); /* 比较两个 zend_string */ if (!zend_string_equals(FOO,foobar)) { /* 复制一个字符串并将其转为小写 */ foo_lc = zend_string_tolower(foo); } php_printf("This is FOO in lower-case: %sn",ZSTR_VAL(foo_lc)); /* 释放内存 */ zend_string_release(FOO); zend_string_release(bar); zend_string_release(foobar); zend_string_release(foo_lc); 通过 zval 访问 zend_string现在你已经知道了如何管理和操作
使用宏你将可以将 zval myval; zend_string *hello,*world; zend_string_init(hello,"hello",strlen("hello"),0); /* 将字符串存入 zval */ ZVAL_STR(&myval,hello); /* 从 zval 中的 zend_string 读取 C 字符串 */ php_printf("The string is %s",Z_STRVAL(myval)); zend_string_init(world,"world",strlen("world"),0); /* 更改 myval 中的 zend_string:使用另一个 zend_string 替换它 */ Z_STR(myval) = world; /* ... */ 你必须记住,以
所有以
还有一些其他你可能用不上的宏。 PHP 的历史和 C 中的典型的字符串简单介绍一下经典的 C 字符串。在 C 语言中,字符串是字符数组( 在 PHP 7 之前, 只要有可能:就使用 内部的 zend_string这里简单介绍一下 interned strings。在扩展开发中很少需要用到这样的概念。Interned 字符串也时常与 OPCache 扩展交互。 Interned 字符串是去重复的字符串。当与 OPCache 一起使用时,它们还会从一个请求重复使用到另一个请求中。 假设你想创建字符串 “foo”。你要做的只是简单地创建一个新的字符串 “foo”: zend_string *foo; foo = zend_string_init("foo",0); /* ... */ 但问题来了:在你需要使用它之前,那个字符串不是已经被创建了吗?当你需要一个字符串时,你代码在 PHP 生命周期中的某个时刻执行,这意味着在你之前的一些代码可能需要完全一样的字符串(也就是示例中的 “foo”)。 Interned 字符串是会要求引擎探测已经存储了的 interned 字符串,并重用已经分配的指针(如果它能找到你的字符串)。如果找不到:创建一个新的字符串并将其标识为 “interned” 字符串。这样它可用于 PHP 源码以外的场景(其他扩展,引擎本身,等等) 这里有一个示例: zend_string *foo; foo = zend_string_init("foo",0); foo = zend_new_interned_string(foo); php_printf("This string is interned : %s",ZSTR_VAL(foo)); zend_string_release(foo); 在上面的代码中,我们创建了一个新的非常经典的 你必须注意一下这里的内存分配。Interned 字符串总是将 refcount 设为一,因为它们不需要被再次引用计数,因为它们将与 Interned 字符串缓冲区共享,因此它们不能被销毁。 示例: zend_string *foo,*foo2; foo = zend_string_init("foo",0); foo2 = zend_string_copy(foo); /* increments refcount of foo */ /* refcount falls back to 1,even if the string is now * used at three different places */ foo = zend_new_interned_string(foo); /* 当 foo 是 interned 字符串,这里什么也不会做 */ zend_string_release(foo); /* 当 foo2 是 interned 字符串,这里什么也不会做 */ zend_string_release(foo2); /* 进程结束后,PHP 将清楚 interned 字符串缓冲区,并且因此会对 "foo" 字符串进行 free() */ 接下来是关于垃圾回收的部分。 当字符串是 interned 时,它的 GC 标志将被修改为 如果 OPCache 被触发,这个过程实际上要更加复杂一些。OPCache 扩展改变了使用 interned 字符串的方式。没有 OPCache 时,如果你在请求的过程中创建了一个 interned zend_string,该字符串将在当前请求的结束时被清除,并且不会在下一个请求中重用。但是,如果你使用了 OPCache,则会将 interned 字符串存储在共享内存块中,并在共享同一个“池”的每个 PHP 进程之间,会共享此内存块。此外,在多个请求之间可以复用 interned 字符串。 使用 Interned 字符串会节省内存,因为同一个字符串在内存中的存储次数不会超过一次。但是,它可能会浪费一些 CPU 时间,因为它经常需要查找存储了的 interned 字符串,即使这个过程已经优化很多了。作为一个扩展设计者,以下是一些全局规则:
Interned 字符串详情可以参考Zend/zend_string.c (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |