最近看内网看到一位大神写的关于几篇高性能处理字符串的帖子,自己总结了一下,主要是围绕如何进行高性能处理字符串判空的
如何高性能的进行字符串判空,这里面涉及两个会影响性能的问题
- 如何遍历字符串数组中的每个value
- 如何判断char中是否存在空格
问题一
基于JDK8源码中,如果使用charAt去遍历那么看charAt中的源码实现如下
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
其中的每次会进行边界判断,这种效率肯定不高,那么还有如下两种方法可以采纳,反射或者Unsafe
反射
static Field valueField;
static {
try {
valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
} catch (NoSuchFieldException ignored) {}
}
char[] chars = (char[]) valueField.get(str);
Unsafe
static long valueFieldOffset;
static {
try {
Field valueField = String.class.getDeclaredField("value");
valueFieldOffset = UNSAFE.objectFieldOffset(valueField);
} catch (NoSuchFieldException ignored) {}
}
char[] chars = (char[]) UNSAFE.getObject(str, valueFieldOffset);
经过对比发现Unsafe的效率要比Reflect好上3倍左右
问题二
如何判断char的值是否为’ ',\n,\r,\f,\t,\b,比较常见的处理方式就是下面几种
每次都要经过6次判断,性能不好
boolean space = ch == ' '
|| ch == '\n'
|| ch == '\r'
|| ch == '\f'
|| ch == '\t'
|| ch == '\b';
采用switch的方式,在JDK高版本中性能较好,但是在大量空字符串中,性能下降的很厉害
boolean space;
switch (ch) {
case ' ':
case '\n':
case '\r':
case '\t':
case '\b':
case '\f':
space = true;
break;
default:
space = false;
break;
}
最终给出了一个采用位运算的方式进行判断,经过评测效率最好
public static void main(String[] args) throws Exception
{
char ch = '1';
long SPACE = (1L << ' ') | (1L << '\n') | (1L << '\r') | (1L << '\f') | (1L << '\t') | (1L << '\b');
boolean space = ch <= ' ' && ((1L << ch) & SPACE) != 0;
System.out.println(space);
char ch1 = ' ';
boolean space1 = ch1 <= ' ' && ((1L << ch1) & SPACE) != 0;
System.out.println(space1);
}
之前在位运算涉及的不深,蒙的一看这段逻辑还是看不太懂,后面好好做了一下功课,下面解读一下这里面的几个小技巧
1.(1L << ' ')
细节一: 选择常量1
位运算中逻辑左移,转换为2进制的话就是常量✖️2的位移次方
,那么位移本身其实并不重要,重要的是前面选择的常量,因为后面不管是2的几次方,那么在二进制的话,都只有一位为1的,那么前面选择常量1可以保证不管这个字符是什么,都只会在一个位置上出现
细节二:1为Long型
可以极大的保证位移后,二进制的位数
下面是\n的二进制数和用1左移后的二进制数
\n的二进制位===========
10 binary value is
0000000000000000000000000000000000000000000000000000000000001010
1024 binary value is
0000000000000000000000000000000000000000000000000000010000000000
2.(1L << ' ') | (1L << '\n') | (1L << '\r') | (1L << '\f') | (1L << '\t') | (1L << '\b')
每个字符在经过逻辑左移后,那么在按位与,可以保证最后的结果中,每个字符都只占一个二进制位,这种方式可以供后面的逻辑判断
' '的二进制位===========
32 binary value is
0000000000000000000000000000000000000000000000000000000000100000
4294967296 binary value is
0000000000000000000000000000000100000000000000000000000000000000
\n的二进制位===========
10 binary value is
0000000000000000000000000000000000000000000000000000000000001010
1024 binary value is
0000000000000000000000000000000000000000000000000000010000000000
\r的二进制位===========
13 binary value is
0000000000000000000000000000000000000000000000000000000000001101
8192 binary value is
0000000000000000000000000000000000000000000000000010000000000000
\f的二进制位===========
12 binary value is
0000000000000000000000000000000000000000000000000000000000001100
4096 binary value is
0000000000000000000000000000000000000000000000000001000000000000
\t的二进制位===========
9 binary value is
0000000000000000000000000000000000000000000000000000000000001001
512 binary value is
0000000000000000000000000000000000000000000000000000001000000000
\b的二进制位===========
8 binary value is
0000000000000000000000000000000000000000000000000000000000001000
256 binary value is
0000000000000000000000000000000000000000000000000000000100000000
按位与后的二进制数
4294981376 binary value is
0000000000000000000000000000000100000000000000000011011100000000
-
((1L << ch) & SPACE) != 0
将要判断的字符使用同一个常量进行位移操作,然后按位与之前计算好的二进制位,按位与返回的结果为,匹配到位的和,那么不等于0就是匹配到了任意一个字符 -
ch <= ' '
一种小优化ascii码中空字符串的十进制最小既为空字符串,这样可以减少一些无意义的判断
最后要说位运算还是性能最高的,虽然看起来代码晦涩了一些