Nginx-string解析
来源:互联网 发布:python 自动化测试 编辑:程序博客网 时间:2024/05/17 13:13
我们先看看ngx_string中定义的几个数据结构
typedef struct { size_t len; u_char *data;} ngx_str_t;这个结构体就是最基础的nginx中的字符串结构了,其中 len表示这个字符串的长度, *data是一个指向无符号char类型的指针。
从这个结构体可以看出,后续关于字符串长度的操作strlen()都可以直接饮用len这个字段。
除了这个最基本的结构体之外,还定义了另外两个字符串相关的结构体:
typedef struct { ngx_str_t key; ngx_str_t value;} ngx_keyval_t;这个结构体从定义来看就可以知道,这是一个关于字符串类型的key,value对的子结构,具体再哪能用到,还得往后看才能知道。
typedef struct { unsigned len:28; unsigned valid:1; unsigned no_cacheable:1; unsigned not_found:1; unsigned escape:1; u_char *data;} ngx_variable_value_t;
这个结构体有点点奇怪,从定义来看,前面5个成员才占用了一个int,看上去只是ngx_str_t的一个扩展版本,其中
len: 只用了28bit来表示长度即最大字符串的长度不超过1-2^29.
valid:
no_cacheable:
not_fount:
escape:
data: 这个字段和ngx_str_t里的对应字段含义一样。
这里就是ngx_string 里所有的关于字符串的结构体了,下面来看一些关于对于字符串操作的接口和宏定义
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
这个宏可以映射到c++中的构造函数,传入一个字符串,然后构造出一个ngx_str_t的对象。
#define ngx_null_string { 0, NULL }
这个宏实际上是上一个宏的特例,构造了一个空的ngx_str_t。#define ngx_str_set(str, text) \ (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text这个宏也可以看成是一个队ngx_str_t的构造函数,但是与前面ngx_string()宏的区别是,ngx_string()宏里传入的变量必须是一个字符串常量,但是这里的参数text可以是一个变量。
#define ngx_str_null(str) (str)->len = 0; (str)->data = NULL这个宏和之前的ngx_null_string的作用也挺像,也是构造一个空的字符串常量,但是这里是显示的把某一个ngx_str_t对象置空或者可以看出是初始化。
#define ngx_tolower(c) (u_char) ((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)#define ngx_toupper(c) (u_char) ((c >= 'a' && c <= 'z') ? (c & ~0x20) : c)
这两个宏,顾名思义,是将字符c进行大小写转换。
voidngx_strlow(u_char *dst, u_char *src, size_t n){ while (n) { *dst = ngx_tolower(*src); dst++; src++; n--; } }这个函数是将字符串src里的字符全部转成小写字符,值得注意的是,这个函数里没有对指针的有效性进行判断,所以需要在外围传入的时候进行保证,否则就会出现段错误。这个函数就使用到了刚刚定义的ngx_tolower这个宏。 同样的办法也可以定义一个转换成大写的函数。
#define ngx_strncmp(s1, s2, n) strncmp((const char *) s1, (const char *) s2, n)/* msvc and icc7 compile strcmp() to inline loop */#define ngx_strcmp(s1, s2) strcmp((const char *) s1, (const char *) s2)#define ngx_strstr(s1, s2) strstr((const char *) s1, (const char *) s2)#define ngx_strlen(s) strlen((const char *) s) #define ngx_strchr(s1, c) strchr((const char *) s1, (int) c)这一系列的宏定义,实际上是对string.h里一些常见的结构进行了封装,这么做的好处就保证了再Nginx中保持了命名风格保持了一致。
static ngx_inline u_char *ngx_strlchr(u_char *p, u_char *last, u_char c){ while (p < last) { if (*p == c) { return p; } p++; } return NULL;}这个函数的一个奇怪修饰符 ngx_inline这个是一个宏定义,实际上就是c语言中的inline,同样是为了命名规范。这个函数的作用从代码可以看出从指针p位置到last位置开始循环寻找字符c,如果找到了那么返回指针所在的位置,否则就返回空指针
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)#define ngx_memset(buf, c, n) (void) memset(buf, c, n)这两个宏定义,是对memset的封装,主要是对内存初始化的操作。
#if (NGX_MEMCPY_LIMIT)void *ngx_memcpy(void *dst, const void *src, size_t n); #define ngx_cpymem(dst, src, n) (((u_char *) ngx_memcpy(dst, src, n)) + (n))#else/* * gcc3, msvc, and icc7 compile memcpy() to the inline "rep movs". * gcc3 compiles memcpy(d, s, 4) to the inline "mov"es. * icc8 compile memcpy(d, s, 4) to the inline "mov"es or XMM moves. */#define ngx_memcpy(dst, src, n) (void) memcpy(dst, src, n)#define ngx_cpymem(dst, src, n) (((u_char *) memcpy(dst, src, n)) + (n))#endif这段代码首先涉及到在makefile中用宏定义进行条件编译的知识,如果在makefile中编译的时候有 -D NGX_MEMCPY_LIMIT=1 这个参数,那么运行的时候就会执行if后面的声明,否则是使用的else里面的声明。
#if (NGX_MEMCPY_LIMIT)void *ngx_memcpy(void *dst, const void *src, size_t n){ if (n > NGX_MEMCPY_LIMIT) { ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "memcpy %uz bytes", n); ngx_debug_point(); } return memcpy(dst, src, n);}#endif看到这段代码就明白NGX_MEMCPY_LIMIT这个参数的意思了,它是对内存拷贝长度的一个限制,如果拷贝的长度超过了这个限制,那么是会报错的。这个函数实际上就是对系统函数memcpy的一个封装返回的是dst的起始地址。感兴趣的可以进一步的去考察memcpy这个函数的实现方式,也比较有意思。
#define ngx_cpymem(dst, src, n) (((u_char *) ngx_memcpy(dst, src, n)) + (n))这个宏定义很有意思,调用了ngx_memcpy,最后宏返回的结果是dst+n,也就是拷贝后的最后一个指针的位置,具体为什么要这么做还需要往后看才知道具体的应用场景。
#define ngx_memcpy(dst, src, n) (void) memcpy(dst, src, n)#define ngx_cpymem(dst, src, n) (((u_char *) memcpy(dst, src, n)) + (n))如果编译的时候没有定义NGX_MEMCPY_LIMIT,那么 上面定义的ngx_memcpy以及ngx_cpymem实现是对memcpy的一个简单封装。但是都做到了接口统一。
#if ( __INTEL_COMPILER >= 800 )/* * the simple inline cycle copies the variable length strings up to 16 * bytes faster than icc8 autodetecting _intel_fast_memcpy() */static ngx_inline u_char *ngx_copy(u_char *dst, u_char *src, size_t len){ if (len < 17) { while (len) { *dst++ = *src++; len--; } return dst; } else { return ngx_cpymem(dst, src, len); } }#else#define ngx_copy ngx_cpymem#endif这段代码可以看出,源码作者对于nginx的性能真的是苦心孤诣。从实现来看,如果字符串的长度不超过16个字节并且要符合某个cpu架构的条件下直接循环逐个字符的复制,比直接调用系统函数要快!
#define ngx_memmove(dst, src, n) (void) memmove(dst, src, n)#define ngx_movemem(dst, src, n) (((u_char *) memmove(dst, src, n)) + (n))这两个宏是关于内存的移动,其实前面有不少关于内存移动的函数了,比如ngx_memcpy,但是为什么这里还要对memmove进行封装呢? 原因是:当dst和src在内存出现重叠的时候,memmove能保证dst和src一样,但是src的内容会被修改
用一小段代码来说明下:
#include<stdio.h>#include <string.h>int main(){ char s[]="hello word!"; memmove(s,s+3,4); printf("%s\n",s); return 0;}
这个代码的输出为:lo wo word!。
#define ngx_memcmp(s1, s2, n) memcmp((const char *) s1, (const char *) s2, n)这个宏定义是对两段长度为n的内存的大小比较,是对系统函数memcmp的一个简单封装。
u_char *ngx_cpystrn(u_char *dst, u_char *src, size_t n){ if (n == 0) { return dst; } while (--n) { *dst = *src; if (*dst == '\0') { return dst; } dst++; src++; } *dst = '\0'; return dst;}这个函数是一个字符串的拷贝函数,从src中拷贝n个字符到dst中。
u_char *ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src){ u_char *dst; dst = ngx_pnalloc(pool, src->len); if (dst == NULL) { return NULL; } ngx_memcpy(dst, src->data, src->len); return dst;}这个函数是想从pool中分配一个和str同样长度的空间,然后把src中的数据dump到pool中来。
u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);u_char * ngx_cdecl ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...); u_char * ngx_cdecl ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...);u_char *ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args);#define ngx_vsnprintf(buf, max, fmt, args) \ ngx_vslprintf(buf, buf + (max), fmt, args)这几个函数是一系列的print函数,是通过格式化的方式将一些变量写入到另外的一个字符串中
u_char * ngx_cdeclngx_sprintf(u_char *buf, const char *fmt, ...) { u_char *p; va_list args; va_start(args, fmt); p = ngx_vslprintf(buf, (void *) -1, fmt, args); va_end(args); return p;}这个函数是处理动态参数个数的典型例子,关于函数动态参数的可以参考【函数动态参数处理详解】 。 另外ngx_vslprintf这个函数实际上是对自动机,对于各种格式化输出的处理,可以自行参考代码。
ngx_int_t ngx_strcasecmp(u_char *s1, u_char *s2); ngx_int_t ngx_strncasecmp(u_char *s1, u_char *s2, size_t n);这两个函数是比较两个字符串而且是大小写不敏感。
ngx_int_tngx_strcasecmp(u_char *s1, u_char *s2) { ngx_uint_t c1, c2; for ( ;; ) { c1 = (ngx_uint_t) *s1++; c2 = (ngx_uint_t) *s2++; c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1; c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2; if (c1 == c2) { if (c1) { continue; } return 0; } return c1 - c2; } }看下ngx_strcasecmp这个函数,实际上在函数内部把u_char字符转换成了ngx_uint. 而ngx_uint实际上是 uintptr_t. 这里把u_char转换成ngx_unit的原因是:转换后比较操作能稍微快一些。
而uintptr_t的定义在/usr/includ/stdint.h头文件中
#if __WORDSIZE == 64# ifndef __intptr_t_definedtypedef long int intptr_t;# define __intptr_t_defined# endiftypedef unsigned long int uintptr_t; #else# ifndef __intptr_t_definedtypedef int intptr_t;# define __intptr_t_defined# endiftypedef unsigned int uintptr_t;#endif
从这段代码可以看出,uintptr_t要么是int,要么是unsigned int。 对于64位的机器是 long 或者unsigned long。
u_char *ngx_strstrn(u_char *s1, char *s2, size_t n); u_char *ngx_strcasestrn(u_char *s1, char *s2, size_t n); u_char *ngx_strlcasestrn(u_char *s1, u_char *last, u_char *s2, size_t n);
/* * ngx_strstrn() and ngx_strcasestrn() are intended to search for static * substring with known length in null-terminated string. The argument n * must be length of the second substring - 1. */u_char *ngx_strstrn(u_char *s1, char *s2, size_t n){ u_char c1, c2; c2 = *(u_char *) s2++; do { do { c1 = *s1++; if (c1 == 0) { return NULL; } } while (c1 != c2); } while (ngx_strncmp(s1, (u_char *) s2, n) != 0); return --s1;}这个函数的功能是在s1字符串中找到第一个s2子串出现的位置,没有找到就不返回空指针,否则返回出现的位置。
ngx_strstrn这个函数上面有段注释,很有意思,主要是有两点需要注意
1,s2必须是一个长度已知的静态的字符串。
2, n这个参数必须是s2长度-1,这里为什么要-1呢? 这里还是需要仔细看代码才能明白,因为在内层的do while循环了已经比较了一个字符了,所以就不需要比较开头的字符了,所以需要减掉1,效率,效率,一切都是为了效率。
ngx_strcasestrn这个函数只是一个大小写不敏感的函数而已。
ngx_strlcasestrn这个函数增加了一个参数last,这个函数实现的功能是在s1到last这个区间内找到第一个出现s2子串的位置并且大小写不敏感。
ngx_int_t ngx_rstrncmp(u_char *s1, u_char *s2, size_t n); ngx_int_t ngx_rstrncasecmp(u_char *s1, u_char *s2, size_t n); ngx_int_t ngx_memn2cmp(u_char *s1, u_char *s2, size_t n1, size_t n2);ngx_int_t ngx_dns_strcmp(u_char *s1, u_char *s2);这几个函数从函数名的后缀来看就可以知道是比较函数,那么先来看看第一个的实现
ngx_int_tngx_rstrncmp(u_char *s1, u_char *s2, size_t n){ if (n == 0) { return 0; } n--; for ( ;; ) { if (s1[n] != s2[n]) { return s1[n] - s2[n]; } if (n == 0) { return 0; } n--; } }从代码可以看出,这是一个字符串的逆向比较函数,在实现上也没有有什么特别的地方。
从这个代码基本可以可以推测出ngx_rstrncasecmp这个函数的实现了,基于上一个函数增加大小写不敏感的处理。
ngx_int_tngx_memn2cmp(u_char *s1, u_char *s2, size_t n1, size_t n2){ size_t n; ngx_int_t m, z; if (n1 <= n2) { n = n1; z = -1; } else { n = n2; z = 1; } m = ngx_memcmp(s1, s2, n); if (m || n1 == n2) { return m; } return z;}这个函数从实现上来看一开始从n1和n2中取到了一个 较小的数,然后比较s1和s2的前缀的大小,最后的一个if语句实际上处理了前缀相同但是字符串的总长度不相同的情况,这里看到的是 当前缀相同,但是长度不同的时候,较长的大。
ngx_int_tngx_dns_strcmp(u_char *s1, u_char *s2){ ngx_uint_t c1, c2; for ( ;; ) { c1 = (ngx_uint_t) *s1++; c2 = (ngx_uint_t) *s2++; c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1; c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2; if (c1 == c2) { if (c1) { continue; } return 0; } /* in ASCII '.' > '-', but we need '.' to be the lowest character */ c1 = (c1 == '.') ? ' ' : c1; c2 = (c2 == '.') ? ' ' : c2; return c1 - c2; }}这是一个域名比较的函数,其中需要特别注意的地方是在遇到 dot和minus这两个符号进行比较的时候,实际需要的是 minus > dot,所以在遇到 dot的时候做了特殊处理,转化成了 space, 这三个符号的ASCII值为 {'.':46, '-':45,' ':32}。
ngx_int_t ngx_atoi(u_char *line, size_t n);ngx_int_t ngx_atofp(u_char *line, size_t n, size_t point);ssize_t ngx_atosz(u_char *line, size_t n);off_t ngx_atoof(u_char *line, size_t n);time_t ngx_atotm(u_char *line, size_t n);ngx_int_t ngx_hextoi(u_char *line, size_t n);这几个函数都是关于字符串到数字的转换。
ngx_int_tngx_atofp(u_char *line, size_t n, size_t point){ ngx_int_t value; ngx_uint_t dot; if (n == 0) { return NGX_ERROR; } dot = 0; for (value = 0; n--; line++) { if (point == 0) { //小数点每往后移动一位,point要减掉1,如果point都变成0了,这个循环还没有结束,那么和这个函数的本意就相违背了。 return NGX_ERROR; } if (*line == '.') { if (dot) { //多次找到了dot,那么这个浮点数是非法的。 return NGX_ERROR; } //字符串中找到了dot,那么就进行标记 dot = 1; continue; } if (*line < '0' || *line > '9') { return NGX_ERROR; } value = value * 10 + (*line - '0'); point -= dot; } while (point--) { value = value * 10; } if (value < 0) { return NGX_ERROR; } else { return value; }
这个函数需要认真琢磨下,先说下函数各个参数的含义
line: 这个比较明了就是这个字符串。
n: 这里是表示输出整数的位数。
point: 是指浮点字符串dot往后移动的位数。
这里可以有一个隐含的条件是n-point应该就是line中整数部分位数,如果不相等,那么参数错误了。
这个函数的场景其实还是很少见的,后续见到了再回过头来看看。
void ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src); ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);ngx_int_t ngx_decode_base64url(ngx_str_t *dst, ngx_str_t *src);static ngx_int_t ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis)
这一组函数是关于base64的编解码的接口
voidngx_encode_base64(ngx_str_t *dst, ngx_str_t *src){ u_char *d, *s; size_t len; /*编码后输出的字符集*/ static u_char basis64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; len = src->len; s = src->data; d = dst->data; while (len > 2) { /*因为3个字节刚好是24位可以组合成4个编码后的字符,所以循环判断的时候是len>2*/ *d++ = basis64[(s[0] >> 2) & 0x3f]; //第一个字符的高6位 *d++ = basis64[((s[0] & 3) << 4) | (s[1] >> 4)]; //第一个字符的低2位和第二个字符的高4位 *d++ = basis64[((s[1] & 0x0f) << 2) | (s[2] >> 6)]; //第二个字符的低4位和第三个字符的高2位 *d++ = basis64[s[2] & 0x3f]; //第三个字符的低6位 s += 3; len -= 3; } if (len) { //字符长度不是3的倍数 *d++ = basis64[(s[0] >> 2) & 0x3f]; //剩下字符的第一个字符的高6位 if (len == 1) { //字符串的长度膜3余1 *d++ = basis64[(s[0] & 3) << 4]; //剩下一个字符的低2位变成高2位,后面四位补0 *d++ = '='; //特殊标记 } else { //字符串长度模3余2 *d++ = basis64[((s[0] & 3) << 4) | (s[1] >> 4)]; //剩下字符的第一个字符的低2位和第二个字符的高4位 *d++ = basis64[(s[1] & 0x0f) << 2]; //剩下字符的第二个字符的低4位,不足补0 } *d++ = '='; //特殊标记。 } //从整个编码来看,如果最后没有'='那么原字符串的长度刚好是3的倍数,如果有1个那么模3余2,如果有1个那么是模3余1. dst->len = d - dst->data;}这个是标准的base64的编码方式,具体说明可以参考代码中的注释。
ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);ngx_int_t ngx_decode_base64url(ngx_str_t *dst, ngx_str_t *src);static ngx_int_t ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis)前面两个函数,统一调用的是最下面接口,参数唯一区别是解码表有点点区别。先来看看decode函数
static ngx_int_tngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis){ size_t len; u_char *d, *s; for (len = 0; len < src->len; len++) { if (src->data[len] == '=') { //找到'=',那么后面的任何字符都不是编码得到的了。 break; } /*字符的有效性验证,因为之前都是把字符串当成bit串然后6位为一组进行编码的,那么得到的编码结果每个字符都应该映射回去,如果映射到77了,那么说明出现了错误。*/ if (basis[src->data[len]] == 77) { return NGX_ERROR; } } /*这里为什么要判定模4余1为非法,需要先看看编码结尾部分的处理,就很好理解了。*/ if (len % 4 == 1) { return NGX_ERROR; } s = src->data; d = dst->data; while (len > 3) { *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);//第一个字符的低6位左移2位,和第二个字符的低6位右移4位,这样就还原了第一个字符。 *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);//第二个字符的 *d++ = (u_char) (basis[s[2]] << 6 | basis[s[3]]); s += 4; len -= 4; } if (len > 1) { *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4); } if (len > 2) { *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2); } dst->len = d - dst->data; return NGX_OK;}
这里再简单介绍下编解码的过程。
解码: 解码的时候需要将字符串看成bit串(高位在左,低位在右),然后从左到右依次6bit的去取,因为6bit的数字大小在0-63之间,所以在编码的时候需要制定一个64个字符的编码表,然后用着6bit的数字作为下标取到对应的字符,需要特别注意的是,最后6bit不够的时候是采用低位补0的办法处理。
解码: 编码的时候有一个编码表,那么解码的时候同样是需要的,比如: 在编码的时候第一个6bit假设都是1,那么得到的下标是63,取到的字符是'/',那么在解码的时候用'/'的ASCII值作为下标在解码表中取到的值应该是63,这样就完成了解码。
需要特别注意的是,在对url进行base64的decode的时候,解码表中值为62,63的位置和标准的解码表位置不一样了,分别是'_' 和'-'ASCII值下标对应的值为63和62. 这是因为当url中传递的字符串在传递之前做base64encode的时候编码表示有差异的,如下:
标准的base64编码表: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
url做base64的编码表: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
之所以对最后两个字符做了变化是因为在'+'和'/'在url中传递的时候是会被urlencode成%xx这样的字符。
这里很奇怪的是,没有提供url参数做base64编码的函数。
uint32_t ngx_utf8_decode(u_char **p, size_t n); size_t ngx_utf8_length(u_char *p, size_t n); u_char *ngx_utf8_cpystrn(u_char *dst, u_char *src, size_t n, size_t len);这几个函数是关于字符串的在utf8编码上的一些处理,这里没有utf8的encode函数,但是提供了utf8的decode函数,要了解utf的decode函数需要先看看utf8的编码规则,可以先参考维基百科-UTF8, 另外也可以参考一篇写的比较好的blog:UTF8编码规则
uint32_tngx_utf8_decode(u_char **p, size_t n){ size_t len; uint32_t u, i, valid; u = **p; if (u >= 0xf0) { //leading byte = 11110xxx, 第一个字节不小于0xf0的话,说明这个字符是4字节组成 u &= 0x07; valid = 0xffff; len = 3; } else if (u >= 0xe0) { //leading byte = 1110xxxx, 第一个字节不小于0xe0的话,说明这个字符是3字节组成 u &= 0x0f; valid = 0x7ff; len = 2; } else if (u >= 0xc2) { //leading byte = 110xxxxx, 第一个字节不小于0xc2的话,说明这个字符是2字节组成, // 从utf编码的方式来看这里判断条件应该是 u>=0xc0? 已经在nginx论坛提问了 , 持续关注中。 u &= 0x1f; valid = 0x7f; len = 1; } else { //leading byte = 0xxxxxxx, (*p)++; return 0xffffffff; } if (n - 1 < len) { return 0xfffffffe; } (*p)++; //continue byte while (len) { i = *(*p)++; if (i < 0x80) { return 0xffffffff; } u = (u << 6) | (i & 0x3f); len--; } if (u > valid) { return u; } return 0xffffffff; }
size_tngx_utf8_length(u_char *p, size_t n){ u_char c, *last; size_t len; last = p + n; for (len = 0; p < last; len++) { c = *p; if (c < 0x80) { p++; continue; } //UTF-8 : [0-0x10FFFF] if (ngx_utf8_decode(&p, n) > 0x10ffff) { /* invalid UTF-8 */ return n; } } return len;}
这个函数是实现,计算一个字符串中含有的字符的个数,以为是utf8编码,所以不能简单计算字节数来计算字符个数。
最后string部分还有几个关于url escape的接口和红黑树的接口,这部分后面其他部分再来看看。
- Nginx-string解析
- String解析
- String解析
- String 解析
- nginx 配置$Query String
- 【Nginx】初识nginx---配置文件解析
- Nginx基础. Nginx配置解析
- String和String Pool解析
- Nginx配置解析
- nginx 配置文件解析
- Nginx filter 模块解析
- 解析Nginx负载均衡
- Nginx配置文件解析
- Nginx配置文件解析之一
- nginx配置文件解析
- nginx 配置文件解析--二
- nginx配置文件解析
- 解析nginx负载均衡
- 初来乍到,捣鼓捣鼓前端
- [Android] 直接获取组件的宽高:Activity.onWindowFocusChanged()
- hdu 题目1754 I Hate It (线段树,区间最大值)
- MPEG-2
- Data Structure_hdu_4217(线段树).java
- Nginx-string解析
- CSS实例(四):实现TabView(页签)效果
- APP源码 教程 网页转APP 安卓Andriod应用开发 手机APP 移动网站
- 多重背包二进制优化的思考
- Nginx-array解析
- poj-2182-Lost Cows(线段树)
- 红黑树的实现代码
- A Simple Problem with Integers_poj_3468(线段树).java
- 关于StringUtils类的用法