什么是字符串常量池?

在理解字符串常量前,我们先熟悉一下如何创建一个字符串,在Java中有两种方法可以创建一个字符串对象:

  • 使用new运算符。例如:
    String str = new String("Hello");
  • 使用字符串常量或者常量表达式。例如:
    String str="Hello"; //(字符串常量) 或者
    String str="Hel" + "lo"; //(字符串常量表达式).

这些字符串的创建方式之间有什么区别呢?在Java中,equals方法被认为是对象的值进行深层次的比较,而操作符==是进行的浅层次的比较。equals方法比较两个对象的内容而不是引用。==两侧是引用类型(例如对象)时,如果引用是相同的-即指向同一个对象-则执行结果为真。如果是值类型(例如原生类型),如果值相同,则执行结果为真。equals方法在两个对象具有相同内容时返回真-但是,java.lang.Object类中的equals方法返回真-如果类没有覆盖默认的equals方法,如果两个引用指向同一个对象。

让我们通过下面的例子来看看这两种字符串的创建方式之间有什么区别吧。

    public class DemoStringCreation {
        public static void main(String args[]) {
            String str1 = "Hello";
            String str2 = "Hello";
            System.out.println("str1 and str2 are created by using string literal.");
            System.out.println("    str1 == str2 is " + (str1 == str2));
            System.out.println("    str1.equals(str2) is " + str1.equals(str2));  
            String str3 = new String("Hello");
            String str4 = new String("Hello");
            System.out.println("str3 and str4 are created by using new operator.");
            System.out.println("    str3 == str4 is " + (str3 == str4));
            System.out.println("    str3.equals(str4) is " + str3.equals(str4));  
            String str5 = "Hel" + "lo";
            String str6 = "He" + "llo";
            System.out.println("str5 and str6 are created by using string constant expression.");
            System.out.println("    str5 == str6 is " + (str5 == str6));
            System.out.println("    str5.equals(str6) is " + str5.equals(str6));  
            String s = "lo";
            String str7 = "Hel" + s;
            String str8 = "He" + "llo";
            System.out.println("str7 is computed at runtime.");
            System.out.println("str8 is created by using string constant expression.");
            System.out.println("    str7 == str8 is " + (str7 == str8));
            System.out.println("    str7.equals(str8) is " + str7.equals(str8));  
        }
    }

输出结果为:

str1 and str2 are created by using string literal.
    str1 == str2 is true
    str1.equals(str2) is true
str3 and str4 are created by using new operator.
    str3 == str4 is false
    str3.equals(str4) is true
str5 and str6 are created by using string constant expression.
    str5 == str6 is true
    str5.equals(str6) is true
str7 is computed at runtime.
str8 is created by using string constant expression.
    str7 == str8 is false
    str7.equals(str8) is true

使用相同的字符序列而不是使用new关键字创建的两个字符串会创建指向Java字符串常量池中的同一个字符串的指针。字符串常量池是Java节约资源的一种方式。

字符串常量池

字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中,就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享。例如:

    public class Program
    {
        public static void main(String[] args)
        {
           String str1 = "Hello";  
           String str2 = "Hello"; 
           System.out.print(str1 == str2);
        }
    }

其结果是:

    true

不幸的是,当使用:

    String a=new String("Hello");

一个字符串对象在字符串常量池外创建,即使池里存在相同的字符串。考虑到这些,要避免new一个字符串除非你明确的知道需要这么做!例如:

    public class Program
    {
        public static void main(String[] args)
        {
           String str1 = "Hello";  
           String str2 = new String("Hello");
           System.out.print(str1 == str2 + " ");
           System.out.print(str1.equals(str2));
        }
    }

结果是:

    false true

JVM中有一个常量池,任何字符串至多维护一个对象。字符串常量总是指向字符串池中的一个对象。通过new操作符创建的字符串对象不指向字符串池中的任何对象,但是可以通过使用字符串的intern()方法来指向其中的某一个。java.lang.String.intern()返回一个保留池字符串,就是一个在全局字符串池中有了一个入口。如果以前没有在全局字符串池中,那么它就会被添加到里面。例如:

    public class Program
    {
        public static void main(String[] args)
        {
            // Create three strings in three different ways.
            String s1 = "Hello";
            String s2 = new StringBuffer("He").append("llo").toString();
            String s3 = s2.intern();
    
            // Determine which strings are equivalent using the ==
            // operator
            System.out.println("s1 == s2? " + (s1 == s2));
            System.out.println("s1 == s3? " + (s1 == s3));
        }
    }

输出是:

    s1 == s2? false
    s1 == s3? true

为了优化空间,运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用。这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收。

Java语言规范第三版中的字符串常量

每一个字符串常量都是指向一个字符串类实例的引用。字符串对象有一个固定值。字符串常量,或者一般的说,常量表达式中的字符串都被使用方法 String.intern进行保留来共享唯一的实例。

    package testPackage;
    class Test {
            public static void main(String[] args) {
                    String hello = "Hello", lo = "lo";
                    System.out.print((hello == "Hello") + " ");
                    System.out.print((Other.hello == hello) + " ");
                    System.out.print((other.Other.hello == hello) + " ");
                    System.out.print((hello == ("Hel"+"lo")) + " ");
                    System.out.print((hello == ("Hel"+lo)) + " ");
                    System.out.println(hello == ("Hel"+lo).intern());
            }
    }
    class Other { static String hello = "Hello"; }

编译单元:

    package other;
    public class Other { static String hello = "Hello"; }

产生输出:

    true true true true false true

这个例子说明了六点:

  • 同一个包下同一个类中的字符串常量的引用指向同一个字符串对象;
  • 同一个包下不同的类中的字符串常量的引用指向同一个字符串对象;
  • 不同的包下不同的类中的字符串常量的引用仍然指向同一个字符串对象;
  • 由常量表达式计算出的字符串在编译时进行计算,然后被当作常量;
  • 在运行时通过连接计算出的字符串是新创建的,因此是不同的;
  • 通过计算生成的字符串显示调用intern方法后产生的结果与原来存在的同样内容的字符串常量是一样的。
原文链接: xyzws 翻译: ImportNew.com - lumeng689
译文链接: http://www.importnew.com/10756.html
[ 转载请保留原文出处、译者和译文链接。]



相关文章

发表评论

Comment form

(*) 表示必填项

7 条评论

  1. xuenhua 说道:

    你好,文中讲到用new创建的字符串不在常量池中,按照文中讲解不难理解下面这段代码中为什么hello==xx,xx==yy的结果是false而hello==yy的结果是true。可是我更改了hello的所指向的内容的值之后,yy和xx的所指向的值都变了,这是为什么?
    String hello=”hello world”;
    System.out.println(“Hello: “+hello);
    String xx=new String(“hello world”);
    String yy=”hello world”;
    System.out.println(“xx==hello : “+ (xx==hello));

    Field hello_field=String.class.getDeclaredField(“value”);
    hello_field.setAccessible(true);
    char[] value=(char[])hello_field.get(hello);
    value[5]=’_';//修改hello所指向的值

    System.out.println(“Hello: “+hello);
    System.out.println(“xx==hello : “+ (xx==hello));
    System.out.println(“yy==hello:”+(hello==yy));
    System.out.println(“xx==yy:”+(xx==yy)+”\n”);

    System.out.println(“xx: “+xx);
    System.out.println(“yy: “+yy);
    System.out.println(“Hello: “+hello);
    输出
    Hello: hello world
    xx==hello : false
    Hello: hello_world
    xx==hello : false
    yy==hello:true
    xx==yy:false

    xx: hello_world //xx的结果竟然跟hello的相同
    yy: hello_world
    Hello: hello_world

    Thumb up 0 Thumb down 0

    • 笨笨爱琳琳 说道:

      虽然xx和yy及hello对象不相同,但是从String的内部结构就能知道,以new String(“hello world”)形式产生的新String,其内部字符串数组还是”hello world”这个常量池中string的字符串数组(不完全一致,但这里是成立的),换言之,xx和yy不一样,但xx内部存储字符串的数组和yy内部的字符串数组是一模一样的,构造xx时所用的参数,其实就是hello字符串常量,也就是yy指向的字符串常量。所以修改hello内部的字符串数组,也就同样修改xx内部的字符串数组。

      Thumb up 1 Thumb down 0

  2. JavaNew 说道:

    我只有一个疑问
    String constantStr = “hello”;
    String headObjextStr = constantStr + new String(“world”);
    这个过程中字符串常量池有多少个对象?

    Thumb up 1 Thumb down 0

    • chenssy 说道:

      3 个!
      这个过程有产生的对象可能是1个、2个或者3个
      1个:常量池中存在hello、helloworld,所以只会产生一个world字符串。
      2个:常量池中存在hello或者helloworld。
      3个:hello、helloworld都不存在。

      Thumb up 0 Thumb down 3

  3. Vensent.Wang 说道:

    String s = “lo”;
    String str7 = “Hel” + s;
    在运行时通过连接计算出的字符串是新创建的,因此是不同的;

    可以这样理解:
    String是不可变的,StringBuffer是可变的。任何String的“+”必须要借助StringBuffer才可以完成。因此这段话在编译的时候必然会这样执行:
    String s = “lo”;
    StringBuffer sb = new StringBuffer(s);
    sb.insert(0, “Hel”);
    String str7 = sb.toString();
    因此可以说,str7是新创建的。

    Thumb up 0 Thumb down 0

  4. lq 说道:

    文章提到“在常量池中的这些字符串不会被垃圾收集器回收“ 似乎不对,常量池也会被回收的吧?

    Thumb up 1 Thumb down 1

跳到底部
返回顶部