最简单的php trim函数并不简单

16 Jan 2019 Category: php

字符串的处理在任何程序中应该是最最常见的了吧。php 的trim函数就是用来去除字符串的字符串。最常用的就是去除空格了。但是,这个简单的函数,是否真的像你认为的那样简单呢?

trim函数的定义如下:


trim ( string $str [, string $character_mask = " \t\n\r\0\x0B" ] ) : string

trim是两边去除,还有ltrim 从左边去除,rtrim从右边去除,在php源码中,最终都是通过一个函数处理的。所以一下关于trim是对php 内部统一的trim而言。

源码在ext/standard/string.c中php_trim函数中。

trim函数处理逻辑:

去除一个字符的情况:

if (what_len == 1) {
char p = *what;
if (mode & 1) {
  for (i = 0; i < len; i++) {
    if (c[i] == p) {
      trimmed++;
    } else {
      break;
    }
  }
  len -= trimmed;
  c += trimmed;
}
if (mode & 2) {
  if (len > 0) {
    i = len - 1;
    do {
      if (c[i] == p) {
        len--;
      } else {
        break;
      }
    } while (i-- != 0);
  }
}
}

对于左边去除,遍历字符串的每个字符,把第一个与what不相等的字符的位置作为新字符串的起始位置,同时更新长度

对右边去除,从右边开始遍历,找到第一个不等于what的字符,把字符串长度减去遍历次数。

到这里,新字符串起始位置有了,长度也确定了,然后执行字符串赋复制命令,返回去除之后的字符串

去除多个字符串的情况:

php_charmask((unsigned char*)what, what_len, mask);

if (mode & 1) {
for (i = 0; i < len; i++) {
  if (mask[(unsigned char)c[i]]) {
    trimmed++;
  } else {
    break;
  }
}
len -= trimmed;
c += trimmed;
}
if (mode & 2) {
if (len > 0) {
  i = len - 1;
  do {
    if (mask[(unsigned char)c[i]]) {
      len--;
    } else {
      break;
    }
  } while (i-- != 0);
}
}

首先使用一个mask数据,用于标记那些需要去除的字符串(mask 可以理解为一个以字符ascii值为键值的hash表)。 然后执行操作跟去除一个字符类似,只是结束条件是寻找到第一个不在字符表里的元素。

默认情况:

if (mode & 1) {
  for (i = 0; i < len; i++) {
    if ((unsigned char)c[i] <= ' ' &&
        (c[i] == ' ' || c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == '\v' || c[i] == '\0')) {
      trimmed++;
    } else {
      break;
    }
  }
  len -= trimmed;
  c += trimmed;
}
if (mode & 2) {
  if (len > 0) {
    i = len - 1;
    do {
      if ((unsigned char)c[i] <= ' ' &&
          (c[i] == ' ' || c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == '\v' || c[i] == '\0')) {
        len--;
      } else {
        break;
      }
    } while (i-- != 0);
  }
}

处理方式跟之前一样,只是去除内容限制在ascii码小于32(即空格)的字符。且只去除’\r’,’\t’,’\v’,’\0’,’\n’字符

看到这里,我们所了解到的有一下几点:

在来看php_charmask这个函数

for (end = input+len; input < end; input++) {
c=*input;
if ((input+3 < end) && input[1] == '.' && input[2] == '.'
    && input[3] >= c) {
  memset(mask+c, 1, input[3] - c + 1);
  input+=3;
} else if ((input+1 < end) && input[0] == '.' && input[1] == '.') {
  ...
  ...
  ...
  result = FAILURE;
  continue;
} else {
  mask[c]=1;
}
}

中间省略部分可以不看,只是对非法数据的一个错误返回。

只要看第一个if的内容。如果字符串假设传入内容what=’a..f’。input指针指向a,这个时候满足if条件,在里面执行的操作相当于把a,b,c,d,e,f内容添加到mask中去。所以所trim是可以指定去除区间的trim('abcdefg','a..f')返回内容只剩下g。

下面几个实际的输出更容易理解:

trim去除列表的性质,在多字节处理的时候就会出现问题了,这也就是为什么trim对于中文会产生乱码。

trim(‘品、’,’、’),’品’ utf字符十六进制表示为’e5 93 81’, 字符串’、’的十六进制表示’e3 80 81’。在trim中,按字节计算,utf8中文编码3个字节表示一个汉字。因此相当于trim去掉内容是三个字符。这三个字符的十六进制表示为’e3 80 81’。所以最终返回字符串的十六进制表示为’e5 93’,因为81已经被去除了。

trim(‘的、’,’、’) 就能返回正确结果。因为’的’的十六进制表示’e7 9a 84’。

所以trim并不简单。要时刻记着,trim是去除列表内的所有字符,遇到第一个非列表字符停止!!

评论