Java中主要用以处理字符串的类是String,StringBuffer和StringBuilder。也是面试中的常见考点,一块来温故而知新吧~
String
常见用法
String str1 = "I love China.";
String str2 = new String("I love China.");
String str3 = "I love";
str3 = str3 + "China";
String 内部实现
内部使用一个字符数组表示字符串,定义如下:
private final char value[];
可以根据char数组创建String,定义如下:
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
值得一提的是:String会根据参数新建一个数组,复制内容,还有一些方法的用法也和char数组有关,譬如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);
}
不可变性
和包装类型相似,String类也是不可变类,即对象一经创建,就没法修改了。String被final修饰,不能被继承,内部的char数组也是final的,初始化后不可变。并且String中的一些看似修改的方法,也是通过新建String对象实现的,譬如concat()方法:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
// copyOf方法新建了一块新的字符数组,复制原内容,调用new创建新的String对象。
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
常量字符串
Java中的字符串常量本身也可以调用String类的各种方法,就跟对象一样。
System.out.println("I love China".length());
System.out.println("I love China".contains("love"));
System.out.println("I love China".indexOf("love"));
这些常量就是String类型的对象,被存放与常量池中,每一个常量保存一份,为所有的使用者共享。当通过常量的形式使用一个字符串的时候,使用的就是常量池中对应的String类的对象。
String str1 = "I love China.";
String str2 = "I love China.";
System.out.println(str1== str2);
输出结果是:
true
上面的代码实际上等同于:
String str = new String (new char[]{'I','l','o','v','e','C','h','i','n','a' });
String str1 = str;
String str2 = str;
System.out.println(str1== str2);
实际上只有一个String对象,其他对象都指向它,所以str1==str2;就很好理解了。
但如果出现下面这样的情况:
String str1 = new String ("I love China.");
String str2 = new String ("I love China.");
System.out.println(str1== str2);
输出结果是:
false
是不是很奇怪呢?Java中String类中以String为参数的构造器如下:
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
从中可见,hash作为String中的另一实例变量,表示缓存的hashCode。str1和str2 指向不同的String对象,只是对象内部的value值指向相同的char数组,所以而str1.equals(str2)只是比较内容,所以返回true。
hashCode
上面也提到了hash值,缓存了hashcode方法的值。我们来见识下hashCode的方法,代码如下:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
// 使得hash值与每一个字符的值相关,也和每一个字符的位置相关。而使用31的原因大致是因为一方面可以产生更加分散的散列,即不同的字符串hash值一般不同,另一方面是计算效率更高,当31*h时,可以采用h<<5移位操作代替乘法。当
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
// 如果缓存的hash不为0,直接返回。
return h;
}
String的+和+=运算符
Java编译器会将此过程转换成StringBuilder的append方法。在稍微复杂的情况下,Java编译器可能没有那么智能,会产生过多的StringBuilder,尤其是循环的时候,应该直接使用StringBuilder较为适合。