String是Java语言中最重要的数据类型,但它非基本类型。
1、String对象具有三个基本特点:不变型、常量池的优化、类的final定义。
下面来介绍一下这三个基本特点:
①不变性:
不变性指的是String对象一旦创建,则不能对其进行修改。
②常量池优化
当两个String对象拥有相同的值时,它们只引用常量池中的同一个拷贝。
String str1="abc";
String str2="abc";
String str3=new String("abc");
System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str1==str3.intern());
结果如下:
true
false
true
str1和str2是两个对象引用,引用了同一个对象,故他们的内存地址是相同的,str3重新开辟了一块内存空间,虽然内存地址不同,但str1和str3在常量池中的位置是相同的。
如下图所示:
③类的final定义
我们都知道String类是用final修饰的,是不被继承的,使用final定义,有助于帮助虚拟机寻找机会,内联所有的final方法提高系统效率。
2、String类的内部结构
String类的底层数据结构是char数组,这是结构的第一部分,第二部分是offset偏移量,第三部分是长度
3、String类中的常见方法介绍
3.1、substring
截取子字符串是字符串中很常见的的操作之一。String类提供了如下两个方法:
public String substring(int beginIndex);
public String substring(int beginIndex, int endIndex);
以第二个方法为例,JDK5和6这个方法存在严重的内存泄漏问题,查看具体代码如下:
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
红色部分的String构造函数,如下:
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
为何说会带来内存泄漏呢?那是因为String的原声char数组内容被原封不动的复制到了子串中,如果原始字符串很大,截取的字符串很小,原始字符串还要占用相应的内存空间,这种空间换时间的方式,浪费了不少的内存空间。
大家再来看看JDK7此方法,发现有所改变:
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对象,查看该方法的构造函数:
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// 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);
}
并未保存原始字符串的内容,而是直接截取所需要的字符串内容。
3.2、String的split和StringTokenizer字符串分割
字符串的分割是字符串最常用的处理之一,String中提供了:
public String[] split(String regex)
它提供了非常强大的字符分割功能,传入的参数可以为正则表达式,进行复杂分割。
有时候量大了,它的分割性能并不是太近如意,可以考虑使用高效率的StringTokenizer
public StringTokenizer(String str, String delim) {
this(str, delim, false);
}
3.3、高效率的charAt
在软件开发过程中,很多时候我们需要判断一个字符串是否以某个子串开头或者结尾,通常会使用startsWith和endsWith来判断,使用者两个函数的好处是可读性强、维护起来容易,如果对于性能要求比较高的系统来讲,这种做法不好的地方是性能差,可以考虑使用charAt
例如:
int len = str.length;
if (str.charAt(0) == 'a' &&
str.charAt(1) == 'b') {
//do something
}
if (str.charAt(len-1) == 'a' &&
str.charAt(len-2) == 'b') {
//do somethig
}
使用startsWith和endsWith:
str.startsWith("ab");
str.endsWith("ab"):