String使用频率非常高,无论是在大型还是小型的应用程序都会大量的使用String类。所以,理解并以高性能的方式使用String是非常重要的。
String类提供了很多功能丰富的API,例如substring(),indexOf(),lastIndexOf()等等。String是不可变类,它没有提供任何访问内部状态的方法,即使是substring()这样的看起来是要修改字符串方法也不会真正的修改实例,而是会创建一个新的String对象并返回。String也不能被继承,继承虽然提高了灵活性,但同时也可能会破坏父类的逻辑(因为重写机制),这样会使得String类非常危险,故String的设计者将其设计成不可继承是有很充分的理由的。
下面我们从源码开始慢慢分析上面讲到的特性。
String类有很多各种功能的API,无法一一细说,我就挑选了indexOf()方法来详细讨论讨论。
public int indexOf(int ch) {
return indexOf(ch, 0);
}
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
// Note: fromIndex might be near -1>>>1.
return -1;
}
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
for (int i = fromIndex; i < max; i++) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return indexOfSupplementary(ch, fromIndex);
}
}
indexOf()除了列出来的两种,还有5种重载形式,不过不是很常用,比较常用就是这俩。只接受一个参数的就不多说了,他没有多余的逻辑,直接就调用了indexOf(int ch, int fromIndex)。indexOf(int ch, int fromIndex)方法的第一个参数是要查找的字符,是int类型,关于该参数,JDK文档是这样描述的:
即它是一个Unicode编码的字符(看JDK里注释文档是了解一个方法的最快速的方式),有计算机基础知识的朋友应该不难理解为什么使用整形数值来代替字符,虽然是int类型,但实际上我们使用的时候完全可以直接传递char类型。
String是不可变的,但为什么要设计成不可变的呢?主要有以下几点考虑:
(1)便于实现常量池。常量池里的字符串常量如果可变的话,会导致很多安全问题。
(2)网络安全。网络连接的参数往往都是以字符串的形式出现,如果字符串可变,那就意味着参数可能被篡改,这显然不是我们想看到的。
(3)线程安全。不可变类肯定是线程安全的,不存在多个线程修改共享变量的情况(因为字符串无法修改)。
(4)加快字符串处理速度。因为字符串是不可变的,所以hashcode只需要在对象创建的时候计算一次即可,例如将String作为Map的Key。
那String是如何实现不可变的呢?实现不可变至少需要下面几个步骤:
我们来看看String是如何实现的。
从源码中我们可以看到,String在类上是有fianl修饰,这样整个类的所有方法就都是final方法,不可被继承重写。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
......
}
同时,我们可以看到除了hash之外,其他的成员变量都是final修饰的。如下所示:
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private int hash; // Default to 0
private static final long serialVersionUID = -6849794470754667710L;
private final char value[];
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
我们发现,这和我们最开始讲的第二个原则有些不同。但没关系,那只是一个比较强硬的规则,只要能证明即使不将其设置成final,也不会有问题即可。那CASE_INSENSITIVE_ORDER为什么是public的呢?其实这个成员变量不能算是String的内部状态,只能是算是一个常量,即使被外界访问了,也不会有太大影响。
最后看看有没有一些可能修改String的API,大致浏览了一下API 文档,发现有很多方法都可能修改String,例如substring,replace等。但实际上,这些方法最终没有修改String对象的值,而是根据原String的内容生成了新的String对象,如下substring方法所示:
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
关键看看最后的返回语句,如果beginIndex为0,那么就不需要重新创建对象了,直接返回本身即可,如果不为0,那就创建一个新的String对象,而不是将对象本身进行修改。replace等方法也是一样的,就不再赘述了。
可见,String类基本上是满足上面提到的5个步骤的,只有少部分代码没有遵循,例如hash不是final修饰的,但没关系,只要能保证没有任何外部途径能修改hash值即可。Java string类算是Java中比较重要的一个类,是需要我们完全掌握其用法的,看到这里还没有领悟的小伙伴可以去动力节点在线的Java SE视频课程免费学习。
提枪策马乘胜追击04-21 20:01
代码小兵92504-17 16:07
代码小兵98804-25 13:57
杨晶珍05-11 14:54