String类

作者: ClawHub的技术分享 | 来源:发表于2018-12-25 16:10 被阅读0次
    String对象创建的四种方式

    在 JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。
    8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有四种:
    ①直接使用双引号声明出来的String对象会直接存储在常量池中。
    ②如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中()

    String类的内存原理:

    这里我直接用了原文的代码,但是图我自己画了一下
    1.第一段代码:

        public static void main(String args[]){
            String s1 = new String("1");
            s1.intern();
            String s2 = "1";
            System.out.println(s1 == s2);
    
            String s3 = new String("1") + new String("1");
            s3.intern();
            String s4 = "11";
            System.out.println(s3 == s4);
        }
    

    结果:

    jdk1.6:false false
    jdk1.7:false true
    

    我们先看一下源码中intern方法:

        public static String valueOf(double d) {
            return Double.toString(d);
        }
    
        /**
         * Returns a canonical representation for the string object.
         * <p>
         * A pool of strings, initially empty, is maintained privately by the
         * class {@code String}.
         * <p>
         * When the intern method is invoked, if the pool already contains a
         * string equal to this {@code String} object as determined by
         * the {@link #equals(Object)} method, then the string from the pool is
         * returned. Otherwise, this {@code String} object is added to the
         * pool and a reference to this {@code String} object is returned.
         * <p>
         * It follows that for any two strings {@code s} and {@code t},
         * {@code s.intern() == t.intern()} is {@code true}
         * if and only if {@code s.equals(t)} is {@code true}.
         * <p>
         * All literal strings and string-valued constant expressions are
         * interned. String literals are defined in section 3.10.5 of the
         * <cite>The Java&trade; Language Specification</cite>.
         *
         * @return  a string that has the same contents as this string, but is
         *          guaranteed to be from a pool of unique strings.
         */
        public native String intern();
    }
    

    大意就是说,如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。
    再通过图分别解析一下Jdk1.6和1.7为啥结果不一样

    ①jdk1.6: image.png jdk1.6中,常量池是在perm区的,和堆是独立分开的,所以s1,s3指向的是堆,而s2,s4指向的是perm区,因此s1==s2,s3==s4一定是false。 ②jdk1.7 image.png

    在 jdk7 的版本中,字符串常量池已经从 Perm 区移到正常的 Java Heap 区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称 jdk8 已经直接取消了 Perm 区域,而新建立了一个元区域。应该是 jdk 开发者认为 Perm 区域已经不适合现在 JAVA 的发展了。

    我们可以清晰地看到,str1指向的是堆中的str1对象,而str2指向的是堆中常量池中的1,所以str1==str2返回了false;

    重点说一下s3,s4:(a,b,c三步)
    a. String s3 = new String("1") + new String("1");
    这句代码主要生成了 s3引用指向的对象。此时s3引用对象内容是"11",但是并没有在常量池中,
    b. 接下来s3.intern();
    这一句代码,是将 s3中的“11”字符串放入 String 常量池中,因为此时常量池中不存在“11”字符串,按jdk6 的做法是在常量池中生成一个 "11" 的对象,关键点是 jdk7 中常量池不在 Perm 区域了,常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。(就是说把s3对象在堆中的位置存在了Stringpool中,反正都是“11”,没必要再去创建了)
    c. String s4 = "11";
    这句代码中"11"是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。所以 s4 引用就指向和 s3 一样了。因此最后的比较 s3 == s4 是 true。
    2.第二段代码:

    public static void main(String[] args) {
        String s = new String("1");
        String s2 = "1";
        s.intern();
        System.out.println(s == s2);
    
        String s3 = new String("1") + new String("1");
        String s4 = "11";
        s3.intern();
        System.out.println(s3 == s4);
    }
    

    结果:

    jdk1.6:false false
    jdk1.7:false false
    

    重点讲一下jdk1.7中为啥变成false了,就是变更了一下intern的调用位置,结果怎么就不用了?
    首先第一句String s3 = new String("1") + new String("1");
    主要生成了 s3引用指向的对象。此时s3引用对象内容是"11",但是并没有在常量池中
    然后执行String s4 = "11";声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,因此 s3 和 s4 的引用是不同的。

    jdk1.6和jdk1.7比较结论:

    从上述的例子代码可以看出 jdk7 版本对 intern 操作和常量池都做了一定的修改。主要包括2点:
    ①将String常量池 从 Perm 区移动到了 Java Heap区
    ②String.intern ()方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。

    String,StringBuffer,StringBuilder的异同 image.png

    1.如果要操作少量的数据用String
    2.单线程操作字符串缓冲区 下操作大量数据 用 StringBuilder
    3.多线程操作字符串缓冲区 下操作大量数据 用StringBuffer

    面试常见

        public static void main(String args[]){
            String s1 = "123";
            String s2 = "123";
            System.out.println(s1 == s2);//true
        }
    

    这个就不说了吧,都指向常量池中同一个对象

        public static void main(String args[]){
            String s1 = new String("123");
            String s2 = new String("123");
            System.out.println(s1 == s2);//false
            System.out.println(s1.intern() == s2.intern());//true
        }
    

    s1,s2在堆中开辟了不同的空间,所以是false,但是他们两个在stringpool中是同一个对象,所以调用intern时一样。

        public static void main(String args[]){
            String s1 = "123";
            final String s2 = "12";
            final String s3 = "3";
            String s4 = s2 + s3;
            System.out.println(s1 == s4);//true
        }
    

    因为final变量在编译后会直接替换成对应的值,所以实际上等于s4=”12”+”3”,而这种情况下,编译器会直接合并为s4=”123”,所以最终s1==s4。

        public static void main(String args[]){
            String s1 = "123";
            String s2 = "12";
            String s3 = "3";
            String s4 = s2 + s3;
            System.out.println(s1 == s4);//false
        }
    

    因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。

    相关文章

      网友评论

          本文标题:String类

          本文链接:https://www.haomeiwen.com/subject/sauxlqtx.html