asort 的问题
有一次在使用 php 基本函数 asort 的时候遇到了一个问题:
<?php $arr = [ "nonce_str" => "441469", "timestamp" => "1464334314" ]; asort($arr); var_dump($arr);?>
这样排序出来的结果是:
array(2) { ["nonce_str"]=> string(6) "441469" ["timestamp"]=> string(10) "1464334314"}
WTF?
不应该呀,为什么排序出来 4 在 1 的前面呢,字符串不应该是以字符串比较的方式来排序么?
查阅 php.net 参考文档得知 sort 类函数的第二个参数为 SORT_FLAG:
SORT_REGULAR - 正常比较单元(不改变类型)
SORT_NUMERIC - 单元被作为数字来比较
SORT_STRING - 单元被作为字符串来比较
SORT_LOCALE_STRING - 根据当前的区域(locale)设置来把单元当作字符串比较,可以用 setlocale() 来改变。
SORT_NATURAL - 和 natsort() 类似对每个单元以“自然的顺序”对字符串进行排序。 PHP 5.4.0 中新增的。
SORT_FLAG_CASE - 能够与 SORT_STRING 或 SORT_NATURAL 合并(OR 位运算),不区分大小写排序字符串。
看来默认的排序方式是所谓的 SORT_REGULAR。可是,“正常比较单元”是什么意思呢?“不改变类型”又是指什么,既然不改变类型,那么我的两个字符串就应该以字符串比较 strcmp 的顺序排列吧,也就是说,字符串 "1464334314" 应该在 "441469" 前面才对!带着这些疑问,我找到了 php 的源代码。
源码分析
http://git.php.net/?p=php-src.git;a=blob_plain;f=ext/standard/array.c;hb=42be298b3020337653cfcbdd87698b90006b2197
php-src.git/ext/standard/array.c, 887:
/* {{{ proto bool asort(array &array_arg [, int sort_flags]) Sort an array and maintain index association */PHP_FUNCTION(asort){ zval *array; zend_long sort_type = PHP_SORT_REGULAR; compare_func_t cmp; if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &array, &sort_type) == FAILURE) { RETURN_FALSE; } cmp = php_get_data_compare_func(sort_type, 0); if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 0) == FAILURE) { RETURN_FALSE; } RETURN_TRUE;}/* }}} */
可以看到,进行具体比较顺序控制的函数指针是 cmp,是通过向 php_get_data_compare_func 传入 sort_type 得到的,sort_type 也就是 SORT_REGULAR / SORT_STRING 这样的标记。
php-src.git/ext/standard/array.c, 632:
static compare_func_t php_get_data_compare_func(zend_long sort_type, int reverse) /* {{{ */{ switch (sort_type & ~PHP_SORT_FLAG_CASE) { // ... case PHP_SORT_STRING: if (sort_type & PHP_SORT_FLAG_CASE) { if (reverse) { return php_array_reverse_data_compare_string_case; } else { return php_array_data_compare_string_case; } } else { if (reverse) { return php_array_reverse_data_compare_string; } else { return php_array_data_compare_string; } } break; // ... case PHP_SORT_REGULAR: default: if (reverse) { return php_array_reverse_data_compare; } else { return php_array_data_compare; } break; } return NULL;}/* }}} */
在这个函数中我们可以看到,SORT_REGULAR 采用了 php_array_data_compare 进行比较,而 SORT_STRING 采用了 php_array_data_compare_string 进行比较。
php-src.git/ext/standard/array.c, 370:
/* Numbers are always smaller than strings int this function as it * anyway doesn't make much sense to compare two different data types. * This keeps it consistent and simple. * * This is not correct any more, depends on what compare_func is set to. */static int php_array_data_compare(const void *a, const void *b) /* {{{ */{ // ... if (compare_function(&result, first, second) == FAILURE) { return 0; } ZEND_ASSERT(Z_TYPE(result) == IS_LONG); return Z_LVAL(result);}/* }}} */
php-src.git/ext/standard/array.c, 465:
static int php_array_data_compare_string(const void *a, const void *b) /* {{{ */{ // ... return string_compare_function(first, second);}/* }}} */
现在我们得到了两条调用链:
SORT_REGULAR -> php_get_data_compare_func -> php_array_data_compare -> compare_function;
SORT_STRING -> php_get_data_compare_func -> php_array_data_compare_string -> string_compare_function;
SORT_REGULAR
先从第一条的 compare_function 开始:
http://git.php.net/?p=php-src.git;a=blob_plain;f=Zend/zend_operators.c;hb=42be298b3020337653cfcbdd87698b90006b2197
php-src.git/Zend/zend_operators.c, 1818:
ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) /* {{{ */{ // ... while (1) { switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) { // ... case TYPE_PAIR(IS_STRING, IS_STRING): if (Z_STR_P(op1) == Z_STR_P(op2)) { ZVAL_LONG(result, 0); return SUCCESS; } ZVAL_LONG(result, zendi_smart_strcmp(Z_STR_P(op1), Z_STR_P(op2))); return SUCCESS; // ... } }}/* }}} */
SORT_REGULAR -> php_get_data_compare_func -> php_array_data_compare -> compare_function -> zendi_smart_strcmp;
php-src.git/Zend/zend_operators.c, 2681:
ZEND_API zend_long ZEND_FASTCALL zendi_smart_strcmp(zend_string *s1, zend_string *s2) /* {{{ */{ int ret1, ret2; int oflow1, oflow2; zend_long lval1 = 0, lval2 = 0; double dval1 = 0.0, dval2 = 0.0; if ((ret1 = is_numeric_string_ex(s1->val, s1->len, &lval1, &dval1, 0, &oflow1)) && (ret2 = is_numeric_string_ex(s2->val, s2->len, &lval2, &dval2, 0, &oflow2))) {#if ZEND_ULONG_MAX == 0xFFFFFFFF if (oflow1 != 0 && oflow1 == oflow2 && dval1 - dval2 == 0. && ((oflow1 == 1 && dval1 > 9007199254740991. /*0x1FFFFFFFFFFFFF*/) || (oflow1 == -1 && dval1 < -9007199254740991.))) {#else if (oflow1 != 0 && oflow1 == oflow2 && dval1 - dval2 == 0.) {#endif /* both values are integers overflown to the same side, and the * double comparison may have resulted in crucial accuracy lost */ goto string_cmp; } if ((ret1 == IS_DOUBLE) || (ret2 == IS_DOUBLE)) { if (ret1 != IS_DOUBLE) { if (oflow2) { /* 2nd operand is integer > LONG_MAX (oflow2==1) or < LONG_MIN (-1) */ return -1 * oflow2; } dval1 = (double) lval1; } else if (ret2 != IS_DOUBLE) { if (oflow1) { return oflow1; } dval2 = (double) lval2; } else if (dval1 == dval2 && !zend_finite(dval1)) { /* Both values overflowed and have the same sign, * so a numeric comparison would be inaccurate */ goto string_cmp; } dval1 = dval1 - dval2; return ZEND_NORMALIZE_BOOL(dval1); } else { /* they both have to be long's */ return lval1 > lval2 ? 1 : (lval1 < lval2 ? -1 : 0); } } else { int strcmp_ret;string_cmp: strcmp_ret = zend_binary_strcmp(s1->val, s1->len, s2->val, s2->len); return ZEND_NORMALIZE_BOOL(strcmp_ret); }}/* }}} */
关键来了,这里我们可以看到,zendi_smart_strcmp 先判断需要比较的两个字符串是否是以数字的形式表现的类型。如果是,则将“数字”一样的字符串作为整型或浮点型进行比较,如果不是,则将字符串用 zend_binary_strcmp 进行比较。
php-src.git/Zend/zend_operators.c, 2791:
ZEND_API zend_uchar ZEND_FASTCALL _is_numeric_string_ex(const char *str, size_t length, zend_long *lval, double *dval, int allow_errors, int *oflow_info) /* {{{ */{ const char *ptr; int digits = 0, dp_or_e = 0; double local_dval = 0.0; zend_uchar type; zend_long tmp_lval = 0; int neg = 0; if (!length) { return 0; } if (oflow_info != NULL) { *oflow_info = 0; } /* Skip any whitespace * This is much faster than the isspace() function */ while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r' || *str == '\v' || *str == '\f') { str++; length--; } ptr = str; if (*ptr == '-') { neg = 1; ptr++; } else if (*ptr == '+') { ptr++; } if (ZEND_IS_DIGIT(*ptr)) { /* Skip any leading 0s */ while (*ptr == '0') { ptr++; } /* Count the number of digits. If a decimal point/exponent is found, * it's a double. Otherwise, if there's a dval or no need to check for * a full match, stop when there are too many digits for a long */ for (type = IS_LONG; !(digits >= MAX_LENGTH_OF_LONG && (dval || allow_errors == 1)); digits++, ptr++) {check_digits: if (ZEND_IS_DIGIT(*ptr)) { tmp_lval = tmp_lval * 10 + (*ptr) - '0'; continue; } else if (*ptr == '.' && dp_or_e < 1) { goto process_double; } else if ((*ptr == 'e' || *ptr == 'E') && dp_or_e < 2) { const char *e = ptr + 1; if (*e == '-' || *e == '+') { ptr = e++; } if (ZEND_IS_DIGIT(*e)) { goto process_double; } } break; } if (digits >= MAX_LENGTH_OF_LONG) { if (oflow_info != NULL) { *oflow_info = *str == '-' ? -1 : 1; } dp_or_e = -1; goto process_double; } } else if (*ptr == '.' && ZEND_IS_DIGIT(ptr[1])) {process_double: type = IS_DOUBLE; /* If there's a dval, do the conversion; else continue checking * the digits if we need to check for a full match */ if (dval) { local_dval = zend_strtod(str, &ptr); } else if (allow_errors != 1 && dp_or_e != -1) { dp_or_e = (*ptr++ == '.') ? 1 : 2; goto check_digits; } } else { return 0; } if (ptr != str + length) { if (!allow_errors) { return 0; } if (allow_errors == -1) { zend_error(E_NOTICE, "A non well formed numeric value encountered"); } } if (type == IS_LONG) { if (digits == MAX_LENGTH_OF_LONG - 1) { int cmp = strcmp(&ptr[-digits], long_min_digits); if (!(cmp < 0 || (cmp == 0 && *str == '-'))) { if (dval) { *dval = zend_strtod(str, NULL); } if (oflow_info != NULL) { *oflow_info = *str == '-' ? -1 : 1; } return IS_DOUBLE; } } if (lval) { if (neg) { tmp_lval = -tmp_lval; } *lval = tmp_lval; } return IS_LONG; } else { if (dval) { *dval = local_dval; } return IS_DOUBLE; }}/* }}} */
上面的代码是判断字符串是否为“数字”形式的比较函数。
SORT_STRING
然后我们看看第二条调用链:SORT_STRING -> php_get_data_compare_func -> php_array_data_compare_string -> string_compare_function;
php-src.git/Zend/zend_operators.c, 1729:
ZEND_API int ZEND_FASTCALL string_compare_function(zval *op1, zval *op2) /* {{{ */{ if (EXPECTED(Z_TYPE_P(op1) == IS_STRING) && EXPECTED(Z_TYPE_P(op2) == IS_STRING)) { if (Z_STR_P(op1) == Z_STR_P(op2)) { return 0; } else { return zend_binary_strcmp(Z_STRVAL_P(op1), Z_STRLEN_P(op1), Z_STRVAL_P(op2), Z_STRLEN_P(op2)); } } else { zend_string *str1 = zval_get_string(op1); zend_string *str2 = zval_get_string(op2); int ret = zend_binary_strcmp(ZSTR_VAL(str1), ZSTR_LEN(str1), ZSTR_VAL(str2), ZSTR_LEN(str2)); zend_string_release(str1); zend_string_release(str2); return ret; }}/* }}} */
可以看到,SORT_STRING 最终也使用 zend_binary_strcmp 函数进行字符串比较。下面的代码,是 zend_binary_strcmp 的实现:
php-src.git/Zend/zend_operators.c, 2539:
ZEND_API int ZEND_FASTCALL zend_binary_strcmp(const char *s1, size_t len1, const char *s2, size_t len2) /* {{{ */{ int retval; if (s1 == s2) { return 0; } retval = memcmp(s1, s2, MIN(len1, len2)); if (!retval) { return (int)(len1 - len2); } else { return retval; }}/* }}} */
总结
经过以上分析我们可以得知,SORT_STRING 排序方式的底层实现是 C 语言的 memcmp,也就是说,它对两个字符串从前往后,按照逐个字节比较,一旦字节有差异,就终止并比较出大小。
而 SORT_REGULAR 会智能判断需排序对象的类型,如果两个字符串都是“纯数字”形式的字符串,会以比较整个字符串所代表的十进制整数、浮点数大小的形式进行排序。如果两个字符串不是“纯数字“形式的,才会和 SORT_STRING 一样。
因此,如果需要以字符串 strcmp 方式逐个字节从前往后比较来进行排序,在调用 php 的 sort 类函数的时候请务必使用 SORT_STRING 这个 flag,否则如果两个字符串都是”纯数字“形式的,就会按照它们所代表的数字大小进行排序。
而且需要注意的是,如果两个值的类型不同,那么这样的比较是毫无意义的,也可能会产生意想不到的结果。
最后的测试
最后,我们在欲排序的值最后添加了一个字符 "s",使它们不再是”纯数字“形式的字符串:
<?php $arr = [ "nonce_str" => "441469s", "timestamp" => "1464334314s" ]; asort($arr); var_dump($arr);?>
最后排序的结果变成了:
array(2) { ["timestamp"]=> string(11) "1464334314s" ["nonce_str"]=> string(7) "441469s"}
这才是我们想要的结果。

tomodifyDataNaphPsession,startTheSessionWithSession_start(),然後使用$ _sessionToset,修改,orremovevariables.1)startThesession.2)setthesession.2)使用$ _session.3)setormodifysessessvariables.3)emovervariableswithunset()

在PHP會話中可以存儲數組。 1.啟動會話,使用session_start()。 2.創建數組並存儲在$_SESSION中。 3.通過$_SESSION檢索數組。 4.優化會話數據以提升性能。

PHP會話垃圾回收通過概率機制觸發,清理過期會話數據。 1)配置文件中設置觸發概率和會話生命週期;2)可使用cron任務優化高負載應用;3)需平衡垃圾回收頻率與性能,避免數據丟失。

PHP中追踪用戶會話活動通過會話管理實現。 1)使用session_start()啟動會話。 2)通過$_SESSION數組存儲和訪問數據。 3)調用session_destroy()結束會話。會話追踪用於用戶行為分析、安全監控和性能優化。

利用數據庫存儲PHP會話數據可以提高性能和可擴展性。 1)配置MySQL存儲會話數據:在php.ini或PHP代碼中設置會話處理器。 2)實現自定義會話處理器:定義open、close、read、write等函數與數據庫交互。 3)優化和最佳實踐:使用索引、緩存、數據壓縮和分佈式存儲來提升性能。

phpsessionstrackuserdataacrossmultiplepagerequestsusingauniqueIdStoredInAcookie.here'showtomanageThemeffectionaly:1)startAsessionWithSessionWwithSession_start()和stordoredAtain $ _session.2)

在PHP中,遍歷會話數據可以通過以下步驟實現:1.使用session_start()啟動會話。 2.通過foreach循環遍歷$_SESSION數組中的所有鍵值對。 3.處理複雜數據結構時,使用is_array()或is_object()函數,並用print_r()輸出詳細信息。 4.優化遍歷時,可採用分頁處理,避免一次性處理大量數據。這將幫助你在實際項目中更有效地管理和使用PHP會話數據。

會話通過服務器端的狀態管理機制實現用戶認證。 1)會話創建並生成唯一ID,2)ID通過cookies傳遞,3)服務器存儲並通過ID訪問會話數據,4)實現用戶認證和狀態管理,提升應用安全性和用戶體驗。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

Atom編輯器mac版下載
最受歡迎的的開源編輯器

Dreamweaver CS6
視覺化網頁開發工具

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

Dreamweaver Mac版
視覺化網頁開發工具

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!