数据类型 - 字符串

字符串的数据结构比较简单,牵扯到的东西比较少,我简单展示下源码中的数据结构的定义。

Zend_string数据结构

typedef struct _zend_string zend_string;


struct _zend_string {
zend_refcounted_h gc;
zend_ulong h; /* hash value */
size_t len;
char val[1];
};

这个结构有4个成员。

  • gc : zend_refcounted_h 结构体,主要用来变量的引用计数,用于内存管理,在后续的内存管理将会使用到
  • h: 字符串的Hash Code(使用time 33 Hash算法得到的Hash值)在字符串被用来当数组的key时才初始化,这样如果同一个字符串被多次用来做key,就不会重复计算了。
  • len: 字符串长度
  • val: 一个可变数组,用来存储字符串内容。 我们来看下string 类型在内存分配时候的代码
//file zend_string.c


static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent)
{
   zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent);
   ....
   //进行宏替换后代码如下
   zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(XtOffsetOf(zend_string, val) + len + 1), persistent); //这里的多出来1 就是存储c中的\0 结束字符
}

字符串的二进制安全

学习过C语言的应该知道,字符串中除了最后一个字符外不允许含有\0,否则会被认为是字符串的结束字符,这就导致了C语言的字符串有很多的限制,比如不存储图片、文件等二进制数据。但是PHP就没有这样的限制,它的字符串可以存储二进制数据,并不会出现任何报错,而PHP的这种能力就叫做字符串的二进制安全。

C代码:

char a[] = "aaa\0b";
int len = strlen(a);
printf("%d\n", len); // 3

 

PHP代码:

$a = "aaa\0b";
echo strlen($a).PHP_EOL; //5


快速充电桩~:但是PHP不是C语言写的吗?为什么PHP不会报错?我们再来回顾一下zend_string结构体,还记得成员变量len吗?它是实现二进制安全的关键,我们不需要像C一样通过\0来判定字符串是否被读取完成,而是通过长度len来判断,这样就保证了字符串的二进制安全。

  • 这里先划重点,敲黑板啦!咱们后续的数组等复杂类型的讲解,都是比较依赖HashTable来实现,使用HashTable比较重要的就是选取一个Hash函数,尽量减少Hash冲突,PHP选取实现方式Time 33算法。
  • 关于Time 33算法: 目前是对于字符串Hash最好的哈希算法之一了,原因在于该算法的速度非常快,而且分类非常好(冲突小,分布均匀).
  • 由于我看的PHP源码的版本和鸟哥不一样,Time 33算法实现的位置也有点区别,对于有兴趣的同学,我贴出对应的源码位置

    //file : zend_string.h
    
    
    static zend_always_inline zend_ulong zend_inline_hash_func(const char *str, size_t len)
    {
       register zend_ulong hash = Z_UL(5381);
    
       /* variant with the hash unrolled eight times */
       for (; len >= 8; len -= 8) {
          hash = ((hash << 5) + hash) + *str++;
          hash = ((hash << 5) + hash) + *str++;
          hash = ((hash << 5) + hash) + *str++;
          hash = ((hash << 5) + hash) + *str++;
          hash = ((hash << 5) + hash) + *str++;
          hash = ((hash << 5) + hash) + *str++;
          hash = ((hash << 5) + hash) + *str++;
          hash = ((hash << 5) + hash) + *str++;
       }
       switch (len) {
          case 7: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */
          case 6: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */
          case 5: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */
          case 4: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */
          case 3: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */
          case 2: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */
          case 1: hash = ((hash << 5) + hash) + *str++; break;
          case 0: break;
    	EMPTY_SWITCH_DEFAULT_CASE()
       }
        /* Hash value can't be zero, so we always set the high bit */
    	#if SIZEOF_ZEND_LONG == 8
       	return hash | Z_UL(0x8000000000000000);
    	#elif SIZEOF_ZEND_LONG == 4
        return hash | Z_UL(0x80000000);
    	#else
    	# error "Unknown SIZEOF_ZEND_LONG"
    	#endif
    }

     

你可能感兴趣的:(读PHP7源码日记)