1、概括
(1)String之所以是不可修改的,其实在于类是final,不可继承,所有变量也是final,一旦初始化就不能更改。所以一些关于String的操作,返回的都是新的String对象
(2)从String源代码发现一个以前认识的误区,以前认为private限制成员的访问,A对象不能访问B对象的私有变量,这是错误的。访问限制是在编译时就已经检查了,因此只能针对类而言,对象是在运行时才会创建。
(3)String的各种构造函数,其实核心的就是要从各种源(byte,string,char[])获取到char数组
(4)String是字符串,也就是字符的数组,接收byte数组,必须涉及转码问题,这里是采用StringCoding的decode和encode来实现解码和编码。String也直接处理unicode的代码点,也就是原始的16进制编号。很多的功能实现都是利用Arrays和System的底层实现
(5)比较都是首先比较是否是同一对象,然后逐个字符比较。检索方法indexof,无论检索字符还是字符串都是采用逐个比较的算法,特别是字符串o(nm)复杂度。因此,长文本中的子串查询不要采用String的indexof
2、String源码阅读
(1)replace替换字符时采用的是遍历替换的方式。该方法的实现很有意思,效率并不高,多个一次循环
- public String replace(char oldChar, char newChar) {
- if (oldChar != newChar)//当新旧字符不等时才做替换处理,否则直接返回当前String
- {
- int len = count;
- int i = -1;
- char[] val = value; /* avoid getfield opcode */
- int off = offset; /* avoid getfield opcode */
- while (++i < len) //首先找到第一个旧字符,假如没有被替换字符,则后面不会替换,直接返回
- {
- if (val[off + i] == oldChar)
- {
- break;
- }
- }
- if (i < len) //找到字符后的替换处理
- {
- char buf[] = new char[len];
- for (int j = 0 ; j < i ; j++) //这个循环完全可以在上面找第一个旧字符时同步处理
- {
- buf[j] = val[off+j];
- }
- while (i < len)//替换旧字符之后的字符序列
- {
- char c = val[off + i];
- buf[i] = (c == oldChar) ? newChar : c;
- i++;
- }
- return new String(0, len, buf);
- }
- }
- return this;
- }
为什么要多一次循环,其实可以这么理解。假如实现方法如下,减少了一次循环。但是当String不包含指定字符时依然要生成一个buf占用了空间。但是有一点要确保,当两个String包含相同内容时,在这里其实是指向同一个对象,直接返回this。
- public String replace(char oldChar, char newChar) {
- if (oldChar != newChar)
- {
- int len = count;
- int i = -1;
- char[] val = value; /* avoid getfield opcode */
- int off = offset; /* avoid getfield opcode */
- char buf[]=new char[len];
- boolean hasOldChar=false;
- boolean isOldChar=false;
- while(++i<len)
- {
- char c = val[off + i];
- if(isOldChar=(c==oldChar))
- hasOldChar=true;
- buf[i] = isOldChar? newChar : c;
- i++;
- }
- if(hasOldChar)
- return new String(0,len,buf);
- }
- return this;
- }
这里到底哪种实现方式好呢?
(2)replace,split对于字符序列操作的任务,都是由Pattern、Matcher进行实现的。,替换字符序列时采用的方式是将被替换字符串转化为对应的正则表达式进行替换。
- public String replace(CharSequence target, CharSequence replacement) {
- return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
- this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
- }
(3)String是不可编辑的,所以对于String的所有操作例如:replace、substring等都是返回新的String对象
(4)String的大小写转换:toUpperCase和toLowerCase处理的不仅仅只有英文,还有其他语言,比较复杂,是通过代码点来转换的,所以程序中没有必要不要轻易调用。
(5)trim函数,去除首尾空字符,重点在于只去除空字符' ',代码很清晰,就是通过字符比较确定截取子串的起始位置
- public String trim() {
- int len = count;
- int st = 0;
- int off = offset; /* avoid getfield opcode */
- char[] val = value; /* avoid getfield opcode */
- while ((st < len) && (val[off + st] <= ' ')) {
- st++;
- }
- while ((st < len) && (val[off + len - 1] <= ' ')) {
- len--;
- }
- return ((st > 0) || (len < count)) ? substring(st, len) : this;
- }
(6)String的最后一个函数intern()非常有意思,时native方法,也就是本地方法,调用了系统的实现。String类维护一个字符常量池,字面字符串“ab”这样的,还有字符串常量表达式public static fianl String a="ab";都是保存在这个池中的。如果赋值给一个字符串一个字面字符串,那么它首先到池中寻找,如果存在相同的字符串,则返回这个引用。调用intern()也是这个过程,以下程序可以验证
- String str1 = "a";
- String str2 = "b";
- String str3 = "ab";
- String str4 = str1 + str2;
- String str5 = new String("ab");
- System.out.println(str5.equals(str3)); //true
- System.out.println(str5 == str3); //false,str5是新的字符串,str3指向池中该字符串,所以地址不同
- System.out.println(str5.intern() == str3); //true,str5.intern()从池中查找并返回,其实找到的引用就是str3
- System.out.println(str5.intern() == str4); //false //str4是新的字符串,string的+拼接字符串后返回新的字符串
因此,如果只是简单使用相同字符串的常量,完全可以采用intern()方法,这样就不会产生大量的string对象
(7)String实现了Serializable接口,可进行序列化传输;实现了Comparable接口,可进行比较,直接进行排序;实现了CharSequence接口,该接口定义了charAt,length,toString,subSequence四个函数
- public final class String implements java.io.Serializable, Comparable<String>, CharSequence
3、StringBuffer和StringBuilder源码阅读
(1)StringBuffer和StringBuilder功能相同,区别在于前者时线程安全的,后者是单线程,因为加上了同步机制,后者在单线程下的效率要高于前者。二者和String的区别在于可编辑,不会稍改动就返回新的字符串。
(2)二者接继承了AbstractStringBuilder类,实现了Serializable和CharSequence接口,两个接口不介绍,重点说下AbstractStringBuilder类,该类是抽象类,不能实例化,实现了继承类主要功能:append、insert方法,之所以可编辑,在于可以动态扩展内部保存字符的value数组,默认的策略是倍增。
- void expandCapacity(int minimumCapacity) {
- int newCapacity = (value.length + 1) * 2;
- if (newCapacity < 0) {
- newCapacity = Integer.MAX_VALUE;
- } else if (minimumCapacity > newCapacity) {
- newCapacity = minimumCapacity;
- }
- value = Arrays.copyOf(value, newCapacity);
- }
实现了各种参数的append和insert方法,核心思想就是首先判断是否需要扩展容量,然后copy字符,更新长度。
- public AbstractStringBuilder append(char str[], int offset, int len) {
- int newCount = count + len;
- if (newCount > value.length)
- expandCapacity(newCount);
- System.arraycopy(str, offset, value, count, len);//数组的拷贝基本上都采用System和Array的实现
- count = newCount;
- return this;
- }
最后有1个比较有意思的函数,reverse()将字符串中的所有字符进行反转,代码如下
- public AbstractStringBuilder reverse() {
- boolean hasSurrogate = false;//utf-16编码中有surrogate pair机制,也就是大于65536的字符都用两个16位编码表示,也就是两个char
- int n = count - 1;
- for (int j = (n-1) >> 1; j >= 0; --j)
- {
- char temp = value[j];
- char temp2 = value[n - j];
- if (!hasSurrogate) //当hasSurrogate为false,才进行里面的判断,因为里面复杂度较高,每次判断效率低,因此这种写法值得借鉴
- {
- hasSurrogate = (temp >= Character.MIN_SURROGATE && temp <= Character.MAX_SURROGATE)
- || (temp2 >= Character.MIN_SURROGATE && temp2 <= Character.MAX_SURROGATE);
- }
- value[j] = temp2;
- value[n - j] = temp;
- }
- if (hasSurrogate) {
- // Reverse back all valid surrogate pairs
- for (int i = 0; i < count - 1; i++) {
- char c2 = value[i];
- if (Character.isLowSurrogate(c2)) {//存在两个字符保存的字符,那么上面按照char交换,这里就要将两个字符的情况再换回来,保证解码正确
- char c1 = value[i + 1];
- if (Character.isHighSurrogate(c1)) {
- value[i++] = c1;
- value[i] = c2;
- }
- }
- }
- }
- return this;
- }
(3)StringBuffer和StringBuilder在继承AbstractStringBuilder类的同时并没有干太多的事情,只是StringBuffer在对外的方法中加上同步关键字synchronized保证多线程下的线程安全,后者则没有,保证单线程下的高效率。