细谈JAVA中的String字符串

对于String而言每一个Java开发人员都不陌生,new一个String字符串会生成两个对象保存在Java内存中,为什么会有这样的设计呢?JDK中在String中又有哪些设计缺陷呢?

String对象的特点

在Java语言中,Java设计者对String进行了大量的优化,主要有三个特征:

  • 不变性
  • 生成String常量池
  • final类

1、不变性
String对象一旦生成,则不能再对它进行改变;String的这个特性泛生了 不变模式(immutable),这种特性解决了当一个对象需要被多线程共享并且访问频繁时,可以省略同步和锁等待的时间,从而大幅度提高系统性能。
注:由于不变性,一些String修改的操作,实际上都是依靠生成新的字符串实现的。比如String.substring()、String.concat()方法,它都没有修改原始的字符串,而是生产了一个新的字符串,如果需要一个可以被修改字符串,需要使用StringBuffer或StringBuilder对象。

2、生成String常量池
针对常量池的优化指当两个String对象拥有相同的值时,它们只是引用常量池中的同一个副本。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。

String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1==s2); //fasle
Sytem.out.println(s1==s2.intern()); //false
Sytem.out.println("abc" == s2.intern()); //true

根据以上代码你会得到什么?String.intern()始终和常量池字符串相等,创建String的时候建议使用String s3 = “abc”; 这样会直接应用常量池中的地址,不建议使用new 来创建String字符串。

3、类的final定义
final类型的定义也是String对象的重要特征。作为final类的String对象在系统中不可能有任何的子类,这是对系统安全性的保护,同时,在JDK1.5版本之前的环境中,使用final定义有助于帮助虚拟机寻找机会,内联所有的final方法,从而提高系统效率。在JDK1.5以后,final效果并不明显。

JDK1.7对String的优化

在JDK1.6中,java.lang.String 主要由3部分组成:value数组、offset偏移和count长度,这个机构为内存溢出埋下了伏笔,字符串的实际内容由value、offset和count三者共同决定。如果字符串value数组包含100个字符,而count长度只有1个字节,那么这个String实际上只有1个字符,却占用了100个字节,剩余99个就属于泄露的部分,他们不会被使用,不会被释放,却长期占用内存,直到字符串本身被回收。
JDK1.6中使用String.substring()就可以容易地构建这样的字符串。JDK1.6中String.substring的实现如下:

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);
}

可以看到,在substring的实现中,最终使用了String的构造函数,生成一个新的String对象,改构造函数的时间如下:

String(int offset, int count, char value[]){
	this.value = value;
	this.offset = offset;
	this.count = count;
}

该构造函数并发公用构造函数,正是这个构造函数引起了内存泄露问题,新生成的String并没有中value中获取自己需要的那部分,而是简单地使用了相同的value引用,只是修改了offset和count,以此来确定新的String对象值。

JDK1.7中对String的实现进行的调整,去掉了offset和count值,String的实质性内容仅有value来决定。下面简单的对比String.length()来说明这个问题,如下:

//JDK1.7的实现
public int length(){
	retrun value.length;
}
//JDK1.6的实现
public int length(){
	return count;
}

可以看到,在JDK1.6中,String的长度和value无关,JDK1.7对String.substring()的实现如下:

public String substring(int beginIndex,int endIndex){
	//省略部分无关内容,可自行查看代码
	int subLen = endIndex -biginIndex;
	//省略部分无关内容,可自行查看代码
	return ((beginIndex == 0) && (endIndex == count)) ?this : new String(value,beginIndex,subLen);
}
public String(char value[],int offset, int count){
	//省略部分无关内容,可自行查看代码
	if(offset > value.length - count){
		throw new StringIndexOutOfBoundsException(offset + count);
	}
	this.valuee = Arrays.copyOfRange(value,offset,offset+count);
}

从上述代码可以看到,在新版本的substring()中,不在复用原String的value而是将实际需要的部分做了复制,改问题也得到了完全修复。

注:关于JDK1.7String常量池的的位置也做了修改:1.6中存放在了永久区,1.7中存放在java堆中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值