• Bin 置顶
    2018-05-16
    jdk1.8中,string是标准的不可变类,但其hash值没有用final修饰,其hash值计算是在第一次调用hashcode方法时计算,但方法没有加锁,变量也没用volatile关键字修饰就无法保证其可见性。当有多个线程调用的时候,hash值可能会被计算多次,虽然结果是一样的,但jdk的作者为什么不将其优化一下呢?

    作者回复: 这些“优化”在通用场景可能变成持续的成本,volatile read是有明显开销的;
    如果冲突并不多见,read才是更普遍的,简单的cache是更高效的

     7
     46
  • 公号-云原生程序员
    2018-05-15
    今日String/StringBuffer/StringBuilder心得:


    1 String

    (1) String的创建机理
    由于String在Java世界中使用过于频繁,Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池。其运行机制是:创建一个字符串时,首先检查池中是否有值相同的字符串对象,如果有则不需要创建直接从池中刚查找到的对象引用;如果没有则新建字符串对象,返回对象引用,并且将新创建的对象放入池中。但是,通过new方法创建的String对象是不检查字符串池的,而是直接在堆区或栈区创建一个新的对象,也不会把对象放入池中。上述原则只适用于通过直接量给String对象引用赋值的情况。

    举例:String str1 = "123"; //通过直接量赋值方式,放入字符串常量池
    String str2 = new String(“123”);//通过new方式赋值方式,不放入字符串常量池

    注意:String提供了inter()方法。调用该方法时,如果常量池中包括了一个等于此String对象的字符串(由equals方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并且返回此池中对象的引用。


    (2) String的特性
    [A] 不可变。是指String对象一旦生成,则不能再对它进行改变。不可变的主要作用在于当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁等待的时间,从而大幅度提高系统性能。不可变模式是一个可以提高多线程程序的性能,降低多线程程序复杂度的设计模式。

    [B] 针对常量池的优化。当2个String对象拥有相同的值时,他们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。

    2 StringBuffer/StringBuilder

    StringBuffer和StringBuilder都实现了AbstractStringBuilder抽象类,拥有几乎一致对外提供的调用接口;其底层在内存中的存储方式与String相同,都是以一个有序的字符序列(char类型的数组)进行存储,不同点是StringBuffer/StringBuilder对象的值是可以改变的,并且值改变以后,对象引用不会发生改变;两者对象在构造过程中,首先按照默认大小申请一个字符数组,由于会不断加入新数据,当超过默认大小后,会创建一个更大的数组,并将原先的数组内容复制过来,再丢弃旧的数组。因此,对于较大对象的扩容会涉及大量的内存复制操作,如果能够预先评估大小,可提升性能。

    唯一需要注意的是:StringBuffer是线程安全的,但是StringBuilder是线程不安全的。可参看Java标准类库的源代码,StringBuffer类中方法定义前面都会有synchronize关键字。为此,StringBuffer的性能要远低于StringBuilder。


    3 应用场景    

    [A]在字符串内容不经常发生变化的业务场景优先使用String类。例如:常量声明、少量的字符串拼接操作等。如果有大量的字符串内容拼接,避免使用String与String之间的“+”操作,因为这样会产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。

    [B]在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程环境下,建议使用StringBuffer,例如XML解析、HTTP参数解析与封装。

    [C]在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使用StringBuilder,例如SQL语句拼装、JSON封装等。
    展开

    作者回复: 很到位

     8
     363
  • Hidden
    2018-05-16
    公司没有技术氛围,项目也只是 功能实现就好,不涉及优化,技术也只是传统技术,想离职,但又怕裸辞后的各种压力
     3
     174
  • sea季陪我去看海
    2018-05-16
    作者我有个疑问,String myStr = "aa" + "bb" + "cc" + "dd"; 应该编译的时候就确定了,不会用到StringBuilder。理由是:
    String myStr = "aa" + "bb" + "cc" + "dd";
    String h =aabbccdd
    Mystr ==h 上机实测返回的是true,如果按照你的说法,应该是返回false才对,因为你说拼接用到stringbuilder,那mystr应该是堆地址,h是常亮池地址。
    展开
     5
     66
  • Kongk0ng
    2018-05-16
    编译器为什么不把
    String myStr = "aa" + "bb" + "cc" + "dd";
    默认优化成
    String myStr = "aabbccdd";
    这样不是更聪明嘛
     3
     36
  • Jerry银银
    2018-05-15
    要完全消化一篇文章的所有内容,真得不是一天就能搞定的,可能需要一个月,甚至好几个月。就比如今天的字符串,我觉得这个话题覆盖的面太广:算法角度;易用角度;性能角度;编码传输角度等

    但是好在,我获得见识。接下来,花时间慢慢研究呗,连大师们都花了那么多时间研究,我们多花点时间,很正常嘛😄

    一点学习心得,和大家分享
    展开
    
     36
  • 愉悦在花香的日子里
    2018-05-15
    getBytes和String相关的转换时根据业务需要建议指定编码方式,如果不指定则看看JVM参数里有没有指定file.encoding参数,如果JVM没有指定,那使用的默认编码就是运行的操作系统环境的编码了,那这个编码就变得不确定了。常见的编码iso8859-1是单字节编码,UTF-8是变长的编码。

    作者回复: 不错,莫依赖于不确定因素

    
     33
  • 明翼
    2018-06-25
    回答一下上面一个人的问题,问题是“”String s3 = new String("12") + new String("34");
    s3.intern();
    String s4 = "1234";
    System.out.println(s3 == s4);//true

    求解,为什么在第二段比较中会返回true,从字节码看s3应该就是生成了一个stringbuilder对象完成连接操作后执行了toString, s3不是应该仍然是堆内的对象地址吗?为什么会和常量池中的地址相等?“”



    我之前也是不明白s3为什么等于s4,查了下资料,说是在jdk1.7之后,如果字符串在堆中有实例,那intern方法就会把这个字符串的引用放在字符串常量池里,所以,String s3 = new String("12") + new String("34");这里在字符串常量池里放了一个字符串“12”,一个字符串“34”,在堆里存放他们的运算结果“1234”,然后把“1234”的引用返回给s3,
    s3.intern()这段代码运行时,jvm在堆里先到了字符串“1234”,所以就会把他的引用放到字符串常量池里,这个引用和s3相等,
    String s4 = "1234";这个代码时,会把字符串常量池里“1234”的引用返回给s4,所以s3是等于s4的,


    个人理解,如有不对,请指正,谢谢😜
    展开
     1
     28
  • Van
    2018-09-19
    String myStr = "aa" +"bb" + "cc" +"dd";反编译后并不会用到StringBuilder,老师反编译结果中出现StringBuilder是因为输出中拼接了字符串System.out.println("My String:" + myStr);

    作者回复: 嗯,文中的例子有歧义,确实欠考虑

    
     22
  • Jerry银银
    2018-05-15
    特别喜欢这句话:“仅仅是字符串一个实现,就需要 Java 平台工程师和科学家付出如此大且默默无闻的努力,我们得到的很多便利都是来源于此。”

    我想说,同学们,写代码的时候记得感恩哦😄

    对于字符串的研究,我觉得能很好的理解计算机的本质和训练计算机思维,提升自己解决问题的能力。

    小小的字符串有着诸多巨人的影子
    展开

    作者回复: 非常感谢

    
     21
  • DoctorDeng
    2018-09-21
            String s = new String("1");
            s.intern();
            String s2 = "1";
            System.out.println(s == s2);

            String s3 = new String("1") + new String("1");
            s3.intern();
            String s4 = "11";
            System.out.println(s3 == s4);
      这道面试题不错,即考察了 intern() 的用法,也考察了字符串常量池在不同版本 JDK 的实际存储,具体可以看看美团博客:https://tech.meituan.com/in_depth_understanding_string_intern.html,
    展开

    作者回复: 思路比结论更有价值

     1
     20
  • 王万云
    2018-05-20
    看大神的文章真的提高太多了,而且还要看评论,评论区也都是高手云集
    
     18
  • a man is a man
    2018-10-10
    1.通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
    2.JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与1.7之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。jdk1.7之后,常量池不仅仅可以存储对象,还可以存储对象的引用
    3.在jdk1.7之前,字符串常量存储在方法区的PermGen Space(永久代)。在jdk1.7之后,字符串常量重新被移到了堆中。
    展开
    
     14
  • 肖一林
    2018-05-15
    这篇文章写的不错,由浅入深,把来龙去脉写清楚了

    作者回复: 谢谢认可

    
     13
  • 毛荣荣
    2018-10-25
    首先要明白,Object obj = new Object();
    obj是对象的引用,它位于栈中, new Object() 才是对象,它位于堆中
    举例:String str1 = "abc"; //通过直接量赋值方式,放入字符串常量池
    String str2 = new String(“abc”);//通过new方式赋值方式,不放入字符串常量池
    String str1 = new String("abc");
    Stirng str2 = "abc";
    虽然两个语句都是返回一个String对象的引用,但是jvm对两者的处理方式是不一样的。
    对于第一种,jvm会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。
    对于第二种,jvm首先会在内部维护的strings pool中通过String的 equels 方法查找是对象池中是否存放有该String对象,如果有,则返回已有的String对象给用户,
    而不会在heap中重新创建一个新的String对象;如果对象池中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至strings pool中。
    注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到strings pool里面的,除非程序调用 String的intern方法。

    看下面的代码:
            if(a == b){
                System.out.println("a == b");
            } else {
                System.out.println("a != b");
            }
            //
            if(a == c){
                System.out.println("a == c");
            } else {
                System.out.println("a != c");
            }

            System.out.println("a.equals(b):" + a.equals(b));
            System.out.println("a.equals(c):" + a.equals(c));
            //
            if(a == c){
                System.out.println("a == c");
            } else {
                System.out.println("a != c");
            }

            System.out.println("a.equals(b):" + a.equals(b));
            System.out.println("a.equals(c):" + a.equals(c));

    // 输出结果为:
    a == b
    a != c
    a.equals(b):true
    a.equals(c):true

    展开
    
     12
  • jamie
    2018-06-14

    编译器为什么不把
    String myStr = "aa" + "bb" + "cc" + "dd";
    默认优化成
    String myStr = "aabbccdd";
    这样不是更聪明嘛

    这个我反编译试过,已经优化成aabbccdd了
    展开
    
     11
  • X
    2019-04-23
    看了评论中的,感觉误区还是不少的,这里经过心爱之人极大的帮助,总结如下,
    首先要搞清楚一个概念,就是到底创建了几个对象。基于1.8
    String s1=new String("11");//在堆中创建对象,并且新建一个"11"对象放入常量池

    对比声明式拼接
    String s1=new String("11")+new String("111");//创建5个,分别是堆中的3个:"11" "111" "11111",常量池2个 "11"
    "111"

    看到不同了么,很多人认为,new出来的只在堆里,这就是最大的错误

    我们再说常量池,早知道,常量池里不止是字面量常量,还有引用型常量,这是重点


    intern这个方法是这样说的,如果该对象代表的字符串在常量池可以找到,那么,返回该常量的引用,如果找不到,那么,去堆中查找,堆中存在,则讲堆中对象的引用放入常量池,并返回该引用(看到了么,是将堆中对象的引用放入常量池),如果都没有找到,则讲该字面量放入常量池

    如果大家有兴趣,可以尝试一下,结合不同的创建方式,来调用intern,进行对比
    展开
     1
     10
  • фщэьшух
    2018-08-25
    回答上面的问题,问题如下:
    作者我有个疑问,String myStr = "aa" + "bb" + "cc" + "dd"; 应该编译的时候就确定了,不会用到StringBuilder。理由是:
    String myStr = "aa" + "bb" + "cc" + "dd";
    String h =aabbccdd
    Mystr ==h 上机实测返回的是true,如果按照你的说法,应该是返回false才对,因为你说拼接用到stringbuilder,那mystr应该是堆地址,h是常亮池地址。
    String myStr = "aa" + "bb" + "cc" + "dd"; 应该编译的时候就确定了,不会用到StringBuilder。这句话是对的,在编译阶段就直接合成aaccccdd,然后看常量池里有没有,有也直接引用,没有就在常量池中生成,返回引用。
    那么此时String h =aabbccdd,则就引用了常量池中的,必然返回true
    展开
    
     8
  • 轩尼诗。
    2018-05-31
    String s = new String(“abc”) 创建了几个对象?
    答案1:在字符串常量池中查找有没有“abc”,有则作为参数,就是创建了一个对象;没有则在常量池中创建,然后返回引用,那就是创建了两个对象。
    答案2:直接在堆中创建一个新的对象。不检查字符串常量池,也不会把对象放入池中。
    网上正确答案貌似是两个,求指教到底是哪个!


    第一种可以写成String a = "abc";String s = new String(a) ,那么第一种解释说得通,String a = "abc"会在常量池创建“abc”。但是string类的intern()是在字符串常量池中查找该字符串,有就返回,没有就创建,如果第一种解释说得通,那这个方法就废了。
    
     8
  • echo
    2018-05-16
    String是immutable,在security, Cache,Thread Safe等方面都有很好的体现。
    Security: 传参的时候我们很多地方使用String参数,可以保证参数不会被改变,比如数据库连接参数url等,从而保证数据库连接安全。
    Cache: 因为创建String前先去Constant Pool里面查看是否已经存在此字符串,如果已经存在,就把该字符串的地址引用赋给字符变量;如果没有,则在Constant Pool创建字符串,并将字符串引用赋给字符串变量。所以存在多个引用指向同一个字符串对象,利用缓存有助提高内存开销。
    Thread Safe: 因为String是immutable, 所以它是Automatically thread safe.
    问题:我一直不能很好的理解最后一个体现,到底String是如何体现在thread safe,老师能不能用简短的线程代码给讲讲?非常感激。
    展开
    
     8
我们在线,来聊聊吧