最近在学习GitHub上的一个项目,看到该项目的登录功能有对前端传入的密码字符串调用了trim()方法,用意在于去除用户输入密码的首部和尾部的空格。部分代码如下:
// 用户名
String username = loginData.get(getUsernameParameter());
// 密码
String password = loginData.get(getPasswordParameter());
// 预防后面的空指针异常
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
// 除去首尾的空格
username = username.trim();
复制代码
trim()是String的一个方法,百度了一下发现trim()并不仅仅是去掉字符串“首尾空格”那么简单,以下是学习笔记。
P.S:
参考1:string.trim()究竟去掉了什么?
参考2:String源码中的注释“avoid getfield opcode”
直接上源码
public String trim() {
// len表示实例字符串的长度
int len = value.length;
// st表示一个计数器(游标)
int st = 0;
// 在一个方法中需要大量引用实例域变量的时候,使用方法中的局部变量代替引用可以减少getfield操作的次数,提高性能
char[] val = value;
// 第1个while
while ((st < len) && (val[st] <= ' ')) {
st++;
}
//第2个while
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
复制代码
观察发现:
- 无论第1个还是第2个while都有字符char类型之间的比较,如
val[st] <= ' '
和val[len - 1] <= ' '
- trim()方法的最后调用了subString()方法,说明trim()最后实际上执行的是一个截取字符串的动作
分析:
- 我们知道,因为char类型在ASCII等字符编码表中有对应的数值,char类型之间的比较实际上可直接当做ASCII表对应的整数的比较。我们可以先参考(ASCII码和Unicode字符编码的对照表)[ascii.911cha.com/]。通过编码对照表,我们可以得出的结论是trim()方法实际上trim掉(除掉)了字符串两端Unicode编码小于等于32(\u0020)的所有字符。大白话一点,trim()实际上就是去除掉了字符串中所有的ASCII的控制字符(这是具有实际意义的,因为我们基本在键盘上是没办法敲出这些字符的),让字符串仅保留ASCII可显示字符,如'a'、'B'等。
- 对于subString()方法:
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
复制代码
我们发现最终返回的String对象是new出来的,new出来的对象位于Heap内存中,而不在方法区的常量池中。这也就说明了一个结果:当String实例调用了trim()方法之后,返回的将是一个新的对象。
总结:
- trim()方法除掉的不仅仅是空格,而是除掉了字符串两端Unicode编码小于等于32(\u0020)的所有字符
- trim()调用后,返回的将是一个全新的对象