- 二丶关于字符串的修改问题
- (1)浅谈字符串不可修改
- (2)关于字符串拼接问题
- (3)StringBuffer和StringBuilder
- (4)说一下synchronized给类加锁吧
我们在之前的内容里面就讲过,字符串是不可以修改的!可是关于这一点还是用一个问题来进行引申。
这里不是说不能修改嘛?为啥s1给变了??
那么由这个问题,开始我们接下来的具体内容。
关于解答上面的问题,具体怎么回事要先明白为什么String类型不可以改变。
是不是感觉我语言前后矛盾?一会说可以改一会又说不能改?没关系,我们打开来看一看String这个类具体是咋构造的。
很明显,String类的成员在JDK1.8之后就剩两个了,value和hash,这个类被final修饰不能继承就算了,而且value这个数组还被final和private修饰,一个被final修饰的数组意味着什么?
如果fianl修饰的变量是一个对象,那么对象的引用不可以修改,但是对象的属性可以修改
而这个数组还被private修饰
它被修饰了之后,并没有提供setvalue方法给一个对外公开的的接口,也就是说外部根本没有办法修改char数组。这就意味着一旦初始化就不能修改并且外部不能访问。
综上:
继
承
没
办
法
继
承
,
引
用
想
改
改
不
了
,
属
性
你
想
改
它
一
个
p
r
i
v
a
t
e
color{red}{继承没办法继承,引用想改改不了,属性你想改它一个private}
继承没办法继承,引用想改改不了,属性你想改它一个private
又
不
给
你
权
限
,
所
以
这
是
S
t
r
i
n
g
对
象
不
能
修
改
的
原
因
之
一
color{red}{又不给你权限,所以这是String对象不能修改的原因之一}
又不给你权限,所以这是String对象不能修改的原因之一
那么回到上面,既然说String对象不能修改,为什么我前后S1的的值不一样了呢?要回答这个问题,我们看一看他们的哈希地址一样不一样,如果一样的话,那么物理地址就应该是相同的!
可以看到,这里两个s1的hashcode并不一样,所以在你拼接字符串的时候生成了一个新的对象"Hello World!!!"
那么我们再次来验证一下,这次我们打开字节码文件,看一看常量池当中的情况,再次来证实一下。
为了方便查看,我们改一下原来的代码:
public static void main(String[] args) {
String s1 = "Hello";
s1 += " World!!!";
System.out.println(s1);
}
运行之后的字节码文件是这个样子的
好像有点看不太清楚,那下面给出具体情况:
0 ldc #22 astore_1 3 new #3 6 dup 7 invokespecial #4 : ()V> 10 aload_1 11 invokevirtual #5 14 ldc #6 < World!!!> 16 invokevirtual #5 19 invokevirtual #7 22 astore_1 23 getstatic #8 26 aload_1 27 invokevirtual #9 30 return
然后笔者尽自己努力讲解一下大概的流程。
1.首先是ldc #2,在字符串常量池当中查看是否有 ,如果没有就进 行创建,创建完毕之后把该操作数栈顶元素存储进局部变量表的一号槽位。 2.接着new #3 ,注意了,这里创建了一个对象,不 是String类的对象,而是StringBuilder的对象。创建完毕之后,dup复制操作数 栈顶。 3.invokespecial编译时方法绑定调用方法。也就是调用StringBuilder的构造方 法 4.接下里的这三行就是通过StringBuffer的append方法对字符串进行拼接 11 invokevirtual #5 14 ldc #6 < World!!!> 16 invokevirtual #5 5.拼接完毕之后,调用String的toString方法进行String字符串化。 6.后面进行输出就可以了。
所以这里给出结论:
字
符
串
不
可
修
改
,
任
何
的
修
改
都
会
引
发
一
个
S
t
r
i
n
g
对
象
的
生
成
color{red}{字符串不可修改,任何的修改都会引发一个String对象的生成}
字符串不可修改,任何的修改都会引发一个String对象的生成
和
一
个
S
t
r
i
n
g
B
u
i
l
d
e
r
对
象
的
生
成
。
color{red}{和一个StringBuilder对象的生成。}
和一个StringBuilder对象的生成。
那么字符串不可修改有什么好处呢?
(3)StringBuffer和StringBuilder
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
- 不可变对象是线程安全的.
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
字符串的不可修改不是一成不变的,我们可以通过一些其他的方法来对其进行修改-----字符串构造器
这里的话我们提供两种构造器,StringBuffer和StringBuilder。这两种方法用法基本上是一致的,其内部包含的方法也基本一致(具体可以查看API文档),
但是具体哪里有区别呢?
区
别
是
:
是
否
是
线
程
安
全
的
,
也
就
是
是
否
用
s
y
c
h
r
o
n
i
z
e
d
加
锁
color{red}{区别是:是否是线程安全的,也就是是否用sychronized加锁}
区别是:是否是线程安全的,也就是是否用sychronized加锁
这里我们其他先不谈,先谈一下具体的使用方法和效率吧。
public static void main(String[] args) {
//先是用String创建对象然后调用字符构造器再转回String
String s1 = "开始拼接";
long start = System.currentTimeMillis();//获取开始毫秒值
for (int i = 0; i < 10000; i++) {
s1 += i;
}
long end = System.currentTimeMillis();//获取结束毫秒值
System.out.println(end - start);
//再接着尝试StringBuffer
StringBuffer s2 = new StringBuffer("开始拼接");
start = System.currentTimeMillis();//获取开始毫秒值
for (int i = 0; i < 10000; i++) {
s2.append(i);
}
end = System.currentTimeMillis();//获取结束毫秒值
System.out.println(end - start);
//再接着尝试StringBuilder
StringBuilder s3 = new StringBuilder("开始拼接");
start = System.currentTimeMillis();//获取开始时间
for (int i = 0; i < 10000; i++) {
s3.append(i);
}
end = System.currentTimeMillis();//获取结束时间
System.out.println(end - start);
}
其实结果毋庸置疑,String这种的肯定是最慢的,毕竟拼接一次调用就要调用一次,拼接一下就要调用一次,肯定很久。
但是这里StringBuffer和Stringbuilder我们不好说,因为差值太小了,我们不能肯定。
那么我们放大这两种字符串构造器的输入数据(10000 --> 1000000),然后再来看看:
没错,StringBuilder的速度更快。
那么为什么呢?
我们打开这两种方法的底层代码,因为我们这里就只是调用了append方法,那么我们就只查看这一种方法:
可以发现这两种方法的区别在哪里?
(4)说一下synchronized给类加锁吧给出结论:因为Stringbuilder是非线程安全的,StringBuffer这个类是线程安全的,它在调用的时候使用synchronized保证自己的线程安全。
其实像这样的关系在JAVA里面很常见,就比如:HashMap是非线程安全的,HashTable是线程安全的;ArrayList是非线程安全的,而Vector是线程安全的
这里笔者大致说一下给类加锁的情况。
首先StringBuffer是一个类,那么就是给类加锁。给类加锁其实就是给类的.class对象加锁。(前面也说过类其实就是一个对象)。因为类的.class对象的相关数据存储在永久代元空间当中,而元空间被数据被全局共享。所以给类加锁就相当于类的这样一个全局锁,锁住所有调用该变量的对象。



