资讯专栏INFORMATION COLUMN

Java String常量池

leejan97 / 917人阅读

摘要:前者在中开辟一个常量,并返回相应的引用,而后者是在中开辟一个常量,再返回相应的对象。方法方法,可以在期间将常量加入到常量池。方法返回这个新创建的常量的引用。

1. String实例的初始化

String类型的初始化在Java中分为两类:

一类是通过双引号包裹一个字符来初始化;

另一类是通过关键字new 像一个普通的对象那样初始化一个String实例。

前者在constant pool中开辟一个常量,并返回相应的引用,而后者是在heap中开辟一个常量,再返回相应的对象。所以,两者的reference肯定是不同的:

public static void main(String... args) {
    String s1 = "abcd";
    String s2 = new String("abcd");
    System.out.println(s1 == s2);   // false
}

而constant pool中的常量是可以被共享用于节省内存开销和创建时间的开销(这也是引入constant pool的原因)。例如:

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

结合这两者,其实还可以回答另外一个常见的面试题目:

public static void main(String... args) {
    String s = new String("abcd");
}

这句话创建了几个对象?

首先毫无疑问,"abcd"本身是一个对象,被放于常量池。而由于这里使用了new关键字,所以s得到的对象必然是被创建在heap里的。所以,这里其实一共创建了2个对象。

但tricky的部分是,如果在这个函数被调用前的别的地方,已经有了"abcd"这个字符串,那么它就事先在constant pool中被创建了出来。此时,这里就只会创建一个对象,即创建在heap的new String("abcd")对象。

但String的内存分配,远远没有这么简单。对于String的拼接,需要做更深入的理解和思考。

2. String的拼接

下面看一个问题:

public static void main(String... args) {
    String s1 = "hell" + "o";
    String s2 = "h" + "ello";
    System.out.println(s1 == s2);   // true
    System.out.println(s1 == "hello");  // true
    System.out.println(s2 == "hello");  // true
    System.out.println("hello" == "hello"); // true
    
    // ------------------------
    
    String c1 = "ab";
    String c2 = c1 + "c";
    System.out.println(c2 == "abc");  // false
    
}

前面四个输出其实很容易理解,最终的结果,都指向了constant pool里的一个常量"hello"。但奇怪的是,最后一个输出也是"abc",并且还都是用指向constant pool中常量的变量来做的拼接,但却得到了一个false的结果。

原因是,Java的String拼接有两个规则:

对于拼接的值,如果都是双引号包裹字符串的形式,则将结果放于constant pool,如果constant pool已经有这个值了,则直接返回这个已有值。

而如果拼接的值中,有一个是非双引号包裹字符串的形式,则从heap中开辟一个新的区域保存常量。也即是使用了String变量来做拼接的情况。

在这样的大原则下,对声明为final的String变量需要做更多的考虑:

如果String变量被声明为final时就已经被赋值,则它被编译器自动处理为常量,因而它就会被当作常量池的变量来处理。

public class ConstantPool {
    public static final String s1 = "ab";
    public static final String s2 = "cd";
    
    public static void main(String... args) {
        String s = s1 + s2;
        String ss = "abcd";
        
        System.out.println(s == ss);  // true
    }
}

而如果声明为final的字符串没有在声明时被赋值,则编译器无法事先决定它的准确值,所以依旧会把它当作一个变量来处理。

public class ConstantPool {
    public static final String s1;
    public static final String s2;
    
    static {
        s1 = "ab";
        s2 = "cd";
    }
    
    public static void main(String... args) {
        String s = s1 + s2;
        String ss = "abcd";
        
        System.out.println(s == ss);  // false
    }
}
3. intern()方法

String.intern()方法,可以在runtime期间将常量加入到常量池(constant pool)。它的运作方式是:

如果constant pool中存在一个常量恰好等于这个字符串的值,则inter()方法返回这个存在于constant pool中的常量的引用。

如果constant pool不存在常量恰好等于这个字符串的值,则在constant pool中创建一个新的常量,并将这个字符串的值赋予这个新创建的在constant pool中的常量。intern()方法返回这个新创建的常量的引用。

示例:

public static void main(String... args) {
    String s1 = "abcd";
    String s2 = new String("abcd");

    /**
     * s2.intern() will first search String constant pool,
     * of which the value is the same as s2.
     */
    String s3 = s2.intern();
    // As s1 comes from constant pool, and s3 is also comes from constant pool, they"re same.
    System.out.println(s1 == s3);
    // As s2 comes from heap but s3 comes from constant pool, they"re different.
    System.out.println(s2 == s3); 
}

/**
 * Output:
 *     true
 *     false
 */

回顾到最开始的第一部分,为什么要引入intern()这个函数呢?就是因为,虽然"abcd"是被分配在constant pool里的,但是,一旦使用new String("abcd")就会在heap中新创建一个值为abcd的对象出来。试想,如果有100个这样的语句,岂不是就要在heap里创建100个同样值的对象?!这就造成了运行的低效和空间的浪费。

于是,如果引入了intern()它就会直接去constant pool找寻是否有值相同的String对象,这就极大地节省了空间也提高了运行效率。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77062.html

相关文章

  • java 为什么需要常量

    摘要:常量池探秘每个文件编译为文件后,都将产生当前类独有的常量池,我们称之为静态常量池。文件中的常量池包含两部分字面值和符号引用。方法的调用成员变量的访问最终都是通过运行时常量池来查找具体地址的。其中,表示将一个常量加载到操作数栈。 java中讲的常量池,通常指的是运行时常量池,它是方法区的一部分,一个jvm实例只有一个运行常量池,各线程间共享该运行常量池。 java内存模型中将内存分为堆和...

    Yuanf 评论0 收藏0
  • 方法区到底是个什么鬼

    摘要:那方法区里都存着什么呢先抛结论静态变量常量类信息构造方法接口定义运行时常量池存在方法区中。动态常量池运行时常量池是方法区的一部分,是一块内存区域。文件常量池将在类加载后进入方法区的运行时常量池中存放。 一、方法区与永久代 这两个是非常容易混淆的概念,永久代的对象放在方法区中,就会想当然地认为,方法区就等同于持久代的内存区域。事实上两者是这样的关系: 《Java虚拟机规范》只是规定了有方...

    binaryTree 评论0 收藏0
  • Java虚拟机中,字符串常量到底存放在哪

    摘要:的三种常量池此外,有三种常量池,即字符串常量池又叫全局字符串池文件常量池运行时常量池。开始虚拟机把字符串常量池位置从永久代挪到堆,又彻底取消,把诸如之类的元数据都挪到堆之外管理。 前言 前阵子和朋友讨论一个问题: 字符串常量归常量池管理,那比如 String str = abc; abc这个对象是放在内存中的哪个位置,是字符串常量池中还是堆? 这句代码的abc当然在常量池中,只有n...

    lewinlee 评论0 收藏0
  • 我终于搞清楚了和String有关的那点事儿。

    摘要:为了减少在中创建的字符串的数量,字符串类维护了一个字符串常量池。但是当执行了方法后,将指向字符串常量池中的那个字符串常量。由于和都是字符串常量池中的字面量的引用,所以。究其原因,是因为常量池要保存的是已确定的字面量值。 String,是Java中除了基本数据类型以外,最为重要的一个类型了。很多人会认为他比较简单。但是和String有关的面试题有很多,下面我随便找两道面试题,看看你能不能...

    paulli3 评论0 收藏0
  • java:String

    摘要:类是类它内部的方法也默认被修饰不能重写字符串常量池当这样声明一个字符串会检测字符串常量池中是否存在这个值的字符串如果存在就直接赋值给否则创建一个新的再赋值给当连续用同样的方式声明两个字符串并作比较结果为这个操作符比较的是什么对于基本变量比较 String类是final类,它内部的方法也默认被final修饰,不能重写. 字符串常量池 当这样声明一个字符串 String str = h...

    U2FsdGVkX1x 评论0 收藏0
  • String的内存模型,为什么String被设计成不可变的

    摘要:证明返回常量池中已存在的对象,不等于新建的对象。为什么要设计成一下内容来自发现百度的中文版本基本也是此文的翻译版。总之,安全性和字符串常量池缓存是被设计成不可变的主要原因。 String是Java中最常用的类,是不可变的(Immutable), 那么String是如何实现Immutable呢,String为什么要设计成不可变呢? 前言 关于String,收集一波基础,来源标明最后,不确...

    vspiders 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<