栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JavaSE--认识String类(下)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JavaSE--认识String类(下)

这里是关于后续的补充
  • 二丶关于字符串的修改问题
    • (1)浅谈字符串不可修改
    • (2)关于字符串拼接问题
    • (3)StringBuffer和StringBuilder
    • (4)说一下synchronized给类加锁吧

二丶关于字符串的修改问题

我们在之前的内容里面就讲过,字符串是不可以修改的!可是关于这一点还是用一个问题来进行引申。

这里不是说不能修改嘛?为啥s1给变了??
那么由这个问题,开始我们接下来的具体内容。

(1)浅谈字符串不可修改

关于解答上面的问题,具体怎么回事要先明白为什么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对象不能修改的原因之一

(2)关于字符串拼接问题

那么回到上面,既然说String对象不能修改,为什么我前后S1的的值不一样了呢?要回答这个问题,我们看一看他们的哈希地址一样不一样,如果一样的话,那么物理地址就应该是相同的!

可以看到,这里两个s1的hashcode并不一样,所以在你拼接字符串的时候生成了一个新的对象"Hello World!!!"

那么我们再次来验证一下,这次我们打开字节码文件,看一看常量池当中的情况,再次来证实一下。
为了方便查看,我们改一下原来的代码:

public static void main(String[] args) {
        String s1 = "Hello";
        s1 += " World!!!";
        System.out.println(s1);
    }

运行之后的字节码文件是这个样子的

好像有点看不太清楚,那下面给出具体情况:

 0 ldc #2 
 2 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对象的生成。

那么字符串不可修改有什么好处呢?

  1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
  2. 不可变对象是线程安全的.
  3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
(3)StringBuffer和StringBuilder

字符串的不可修改不是一成不变的,我们可以通过一些其他的方法来对其进行修改-----字符串构造器

这里的话我们提供两种构造器,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方法,那么我们就只查看这一种方法:


可以发现这两种方法的区别在哪里?

给出结论:因为Stringbuilder是非线程安全的,StringBuffer这个类是线程安全的,它在调用的时候使用synchronized保证自己的线程安全。
其实像这样的关系在JAVA里面很常见,就比如:HashMap是非线程安全的,HashTable是线程安全的;ArrayList是非线程安全的,而Vector是线程安全的

(4)说一下synchronized给类加锁吧

这里笔者大致说一下给类加锁的情况。

首先StringBuffer是一个类,那么就是给类加锁。给类加锁其实就是给类的.class对象加锁。(前面也说过类其实就是一个对象)。因为类的.class对象的相关数据存储在永久代元空间当中,而元空间被数据被全局共享。所以给类加锁就相当于类的这样一个全局锁,锁住所有调用该变量的对象。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/309981.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号