trim为什么失效了

前言

一直以为String#trim()是去掉字符串两边空格的。但是以下代码却与预期不同。

1
2
3
4
System.out.println("测试:["+"820000    ".trim()+"]"); // ASCII码 160 的空格
System.out.println("测试:["+" 市辖区"+"]"); // ASCII码 12288 的空格
// 测试:[820000    ]
// 测试:[ 市辖区]

这段数字加空格是我从最新县及县以上行政区划代码爬取的。

查看源码

很明显, 算法就是

  1. 从前往后, 找到第一个非空格的字符
  2. 从后往前, 找到第一个非空格的字符
  3. 使用substring截取字符串
  4. substring使用构造复制函数进行拷贝
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public String trim() {
    int len = value.length;
    int st = 0;
    char[] val = value; /* avoid getfield opcode */

    while ((st < len) && (val[st] <= ' ')) {
    st++;
    }
    while ((st < len) && (val[len - 1] <= ' ')) {
    len--;
    }
    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
    }

    public String substring(int beginIndex, int endIndex) {
    int subLen = endIndex - beginIndex;
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
    : new String(value, beginIndex, subLen);
    }
    明显没毛病, 要我写我也差不多是这样写。

断点调试

trim加上断点, 保险起见, 每条语句加上断点。

1
2
3
4
5
6
7
8
9
10
11
12
13
public String trim() {
· int len = value.length;
· int st = 0;
· char[] val = value; /* avoid getfield opcode */
·
· while ((st < len) && (val[st] <= ' ')) {
· st++;
· }
· while ((st < len) && (val[len - 1] <= ' ')) {
· len--;
· }
· return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}

调试结果如下, 代码走到第9行, 进不去这个while
那明显是(val[len - 1] <= ' ')出了问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
len (slot_1) = 10
st (slot_2) = 0
value = {
0 = '8' 56
1 = '2' 50
2 = '0' 48
3 = '0' 48
4 = '0' 48
5 = '0' 48
6 = ' ' 160
7 = ' ' 160
8 = ' ' 160
9 = ' ' 160
}

这时候注意value[9]的ASCII码为160
查阅ASCII码表可以知道, 空格的ASCII码应该为32
可是这明显是空格啊! 为什么是160呢!

为什么眼见不为实

web中有一个常识是, 要使用连续空格, 必须使用&nbsp;
如果只按住space输入空格的话, 会被压缩为一个空格。

很明显, 这个&nbsp;的ASCII码就是160
trim方法只判断了ASCII码为32的空格。
就算是apache的StringUtils#trim(), 底层也是调用String#trim()的。

后来还发现了一个ASCII码为12288的空格, 一个汉字宽度的空格。

自己动手丰衣足食

参考源码, 进行改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class StringHelper{
/**
* 去除字符串首尾空格
* 32 为 普通空格
* 160 为 html的空格 &nbsp;
* 12288 为 一个汉字宽度的空格
* @param str
* @return
*/
public static String trim(String str) {
if (str == null) {
return null;
}
char[] val = str.toCharArray();
int len = val.length;
int st = 0;

while ((st < len) &&
StringUtils.equalsAny(val[st] + "",
(char) (32) + "",
(char) (160) + "",
(char) (12288) + "")) {
st++;
}
while ((st < len) &&
StringUtils.equalsAny(val[len - 1] + "",
(char) (32) + "",
(char) (160) + "",
(char) (12288) + "")) {
len--;
}
return ((st > 0) || (len < val.length)) ? str.substring(st, len) : str;
}
}