String对象及其特点
String并不是Java的基本数据类型,从String的代码实现来说,它主要包括三个部分:char数组,偏移量和String的长度。char数组表示String的内容,它是String对象所表示的字符串的超集。String的真实内容还需要由偏移量和长度在这个char数组中进行定位和截取。 String对象具有三个基本特点: 不变性:String对象一旦生成,就不能修改它了。String的这个特性可以泛化为不变模式(一种设计模式),即一个对象的状态在被创建后就不再发生变化。不变模式的主要作用在于当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁等待时间,从而大幅度提高系统性能。 针对常量池的优化:当两个String对象拥有相同的值时,它们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。
String str1="abc";
String str2="abc";
String str3=new String("abc");
System.out.println(str1==str2); //返回true
System.out.println(str1==str3); //返回false
System.out.println(str1==str3.intern()); //返回true,使用intern()方法返回该String对象在常量池中的引用
str1和str2引用了相同的地址,但是str3却重新开辟了一块内存空间。但即便如此,str3在常量池中的位置和str1是一样的,也就是说,虽然str3单独占用了堆空间,但是它所指向的实体却和str1完全一样。
类的final定义:作为final类的String不会有任何子类,这是对系统安全性的保护。同时,对于JDK1.5之前的环境中,使用final定义,有助于帮助虚拟机寻找机会,内联所有的final方法,从而提高系统效率。但这种优化方法在JDK1.5之后,效果并不明显。
subString()方法的内存泄漏
截取子字符串是非常常见的操作之一,String提供了两种截取子字符串的方法:
public String subString(int beginIndex);
public String subString(int beginIndex,int endIndex); //从beginIndex开始,到endIndex为止的子字符串;这个方法存在严重的内存泄漏问题
//上述第二个方法的源码实现
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);
}
//上面的代码都是为了检查是否越界的,不重要
//如果要返回的子字符串是字符串本身就直接返回字符串本身
//如果不是,返回一个新建的String对象
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
//subString()方法最后调用的String构造方法
String(int offset,int count,char value[]){
//String原生内容value数组被复制到新的子字符串中,通过偏移量和长度来截取字符串
this.value = value;
this.offset = offset;
this.count = count;
}
//试想一下,如果原始字符串很大,截取的字符串却很短,
//那么生成后的子字符串包含了原生字符串的所有内容,并占据了相应的内存空间,
//而仅仅通过偏移量和长度来决定实际的取值
//这种做法提高了运算速度却浪费了大量的内存空间
//这是新版本的JDK中的构造方法,更改了,解决了内存泄漏的问题
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的split()方法就实现了该方法;
public String[] split(String regex) //regex可以是一个普通一个正则表达式,从而进行复杂逻辑的字符串分割
比如字符串“a;b,c:d”,如果要把这些分隔符去掉,只保留字母内容,只需要使用正则表达式“[;|,|:]"。就简单的字符串分割而言,spilt()方法的性能却不太好。
最原始的字符串分割
直接使用split()方法对字符串进行分割,是最原始的方法。String.split()方法使用简单,功能强大。但是在频繁地使用该方法,性能比较差。
使用效率更高的StringTokenizer类分割字符串
StringTokenizer是JDK中专门用来处理字符串分割成子串的工具类。
//构造函数
public StringTokenizer(String str,String delim) //str为要被分割处理的字符串,delim是分割字符
StringTokenizer st = new StringTokenizer(orgStr,";");
for(int i=0;i<10000;i++){
while(st.hasMoreTokens()){ //知道是否有更多的子字符串需要处理
st.nextToken(); //得到下一个分割的字符串
}
st = new StringTokenizer(orgStr,";"); //这说明使用上述两个方法会改变st对象本身的值,所以需要重新赋值
}
更优化的字符串分割方式
//indexOf()和subString()这两个方法的执行效率非常高,很适合作为高频率的函数使用
String tmp = orgStr;
for(int i=0;i<10000;i++){
while(true){
String splitStr=null;
int j=tmp.indexOf(';'); //找分割符的位置
if(j<0) break; //没有分割符存在
splitStr=tmp.subString(0,j); //找到了分割符,截取前面的子字符串
tmp=tmp.subString(j+1); //剩下的需要处理的字符串
}
tmp=orgStr;
}
高效率的charAt()方法
//String提供的方法
//返回给定的字符串中,位置在index位置的字符
//功能与indexOf()相反,但效率和indexOf()一样高
public char charAt(int index)
//下面的两个方法的效率远低于charAt()方法
//判断字符串是否以prefix字符串开头
public boolean startWith(String prefix)
//判断字符串是否以suffix结尾
public boolean endWith(String suffix)