字符串三兄弟String、StringBuffer与StringBuilder

字符串三兄弟String、StringBuffer与StringBuilder

一、为什么String 是不可变的?

1、String 类 被定义成final类,不可被继承。
2、String底层是由char 数组构成的,而char数组被定义成private final。
在这里插入图片描述

二、为什么要将String设计成不可变?

主要是设计考虑,效率优化,以及安全性这三大方面。

1、字符串常量池的需要

当一个String对象被创建的时候,首先会去常量池中查找,如果找到了就返回对该字符串的引用,如果没找到就创建这个字符串并塞到常量池中。
用new创建的String对象比较特殊,都会在堆内存中存放一个对象,然后再去查找常量池中是否有这个字符串,若没有,会在常量池中再次存放。

//a返回的是 堆内存 中的地址值
String a=new String("a"); //同时 在堆内存中 和 常量池中 存放一个对象
//b返回的是 常量池 中的地址值
String b="a";  //字面量存放在 常量池中
System.out.println(a==b); // false        

正是因为String 的不可变,我们才能实现字符串对象与字面值相同的字符串对象在常量池中共享。

String a=new String("a");
String b=new String("a");
System.out.println(a==b); //false

String c="a";
String d="a";
System.out.println(c==d); //true

如果String对象是可变的,那就不能这样共享,因为一旦对某一个String类型变量引用的对象值改变,将同时改变一起共享字符串对象的其他String类型变量所引用的对象的值。

2、允许String对象缓存HashCode

因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串原因。

3、多线程安全

因为String是不可变的,因此在多线程操作下,它是安全的。同一个字符串实例可以被多个线程共享。

4、 Java 类的参数

字符串已被广泛用作许多 Java 类的参数,例如,网络连接地址URL,文件路径path,还有反射机制所需要的String参数等。
如果 String 是可变的,这将导致严重的安全威胁。如果有人可以访问他有权授权的任何文件,然后就可以故意或意外地更改文件名并获得对该文件的访问权限。

5、String被用于类加载机制

String 不可变的绝对最重要的原因是它被类加载机制使用,因此具有深刻和基本的安全考虑。
如果 String 是可变的,加载“java.io.Writer” 的请求可能已被更改为加载 “mil.vogoon.DiskErasingWriter”.

三、不可变带来的缺点

由于String是不可变的,那么每次对String对象进行修改(增删改)时,都会创建一个新的String对象,而创建新对象有一个比较复杂的过程,十分消耗内存。
而原来的String对象将会被丢弃,等待JVM垃圾回收器GC的清理。若频繁地对字符串进行修改,将会制造大量垃圾,降低程序的性能。
StringBuffer 和 StringBuilder ,可以弥补String 不可变的不足。

四、 StringBuffer 和 StringBuilder 共同点

1、 封装了char[]数组
2、 是可变的字符序列
3、 提供了一组可以对字符内容修改的方法
5、 内部字符数组默认初始容量是16:super(str.length() + 16);
6、 如果大于16会尝试将扩容,新数组大小原来的变成2倍+2。
容量如果还不够,直接扩充到需要的容量大小。
int newCapacity = value.length * 2 + 2;

五、StringBuffer与StringBuilder区别

–StringBuffer是旧版本就提供的 @since JDK1.0,方法中有synchronized同步锁,是线程安全的。
–StringBuilder是jdk1.5后产生 @since 1.5,方法中没有synchronized同步锁,是线程不安全的。

六、总结

1、三者的区别:

String:不可变的字符序列,效率低;
StringBuffer:可变的字符序列,效率一般,线程安全;
StringBuilder:可变的字符序列,效率高,线程不安全;

String、StringBuffer 与 StringBuilder效率PK:

public class Test {

	public static void main(String[] args) {
		//StringTest("abc",100000);
		StringBufferTest("abc",100000000);
		StringBuilederTest("abc",100000000);
	}
	
	public static void StringTest(String str,int a){
		long t=System.currentTimeMillis();
		String s="";
		for(int i=0;i<a;i++){
			s+=str;
		}
		t=System.currentTimeMillis()-t;
		System.out.println("String方法拼接"+a+"次字符串"+str+",耗时:"+t);
	}
	public static void StringBufferTest(String str,int a){
		long t=System.currentTimeMillis();
		StringBuffer s=new StringBuffer();
		for(int i=0;i<a;i++){
			s.append(str);
		}
		t=System.currentTimeMillis()-t;
		System.out.println("StringBuffer方法拼接"+a+"次字符串"+str+",耗时:"+t);
	}
	public static void StringBuilederTest(String str,int a){
		long t=System.currentTimeMillis();
		StringBuilder s=new StringBuilder();
		for(int i=0;i<a;i++){
			s.append(str);
		}
		t=System.currentTimeMillis()-t;
		System.out.println("StringBuilder方法拼接"+a+"次字符串"+str+",耗时:"+t);
	}

}

结果如下:
在这里插入图片描述
效率比较: String < StringBuffer < StringBuilder

StringBuffer 与 StringBuilder 安全性PK:
public class Test5 {

	public static void main(String[] args) throws InterruptedException {
		StringBuilder str1=new StringBuilder();
		StringBuffer str2=new StringBuffer();
		
		for(int i=0;i<10;i++){
			new Thread(){
				@Override
				public void run() {
					for(int j=0;j<10000;j++){
						str1.append("a");
						str2.append("a");						
					}
				};
			}.start();
		}
		
		Thread.sleep(1000);
		System.out.println("StringBuilder:"+str1.length());
		System.out.println("StringBuffer:"+str2.length());
	}

}

结果:StringBuilder存在数据丢失。
在这里插入图片描述
原因:首先StringBuffer的append方法中有synchronized同步锁,它支持线程同步。而StringBuilder中没有同步锁,不支持线程同步。

StringBuffer与StringBuilder中的append方法均继承自同一个父类AbstractStringBuilder。
AbstractStringBuilder中有两个成员变量:
char[] value; //用于字符存储
int count; //用于计算字符的数量
在这里插入图片描述
append方法中:
str.getChars(0, len, value, count); //将字符串str复制到value的目标数组中
count += len; //重新计算字符数组的长度
这两步操作均不是原子操作。只有同步锁才能保证数据的原子性。
假设这个时候count值为10,len值为1,两个线程同时执行到了count += len; 拿到的count值都是10,执行完加法运算后将结果赋值给count,所以两个线程执行完后count值为11,而不是12。这就是为什么测试代码输出的值要比100000小的原因。
在这里插入图片描述
结论:StringBuffer线程安全,StringBuilder线程不安全。

2、如何选择?

(1)如果要操作少量的数据用 String;
(2)多线程操作大量数据用 StringBuffer;
(3)单线程操作大量数据用 StringBuilder。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值