String的比较,编译优化以及intern引申

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介:   java string比较时必须使用equals,这是一个定论,但是要了解这个规则确实需要不少知识。不少书上告诉你==只能比较地址,地址不一样,那就是false,string存在着相同字面量不同地址的问题。

  java string比较时必须使用equals,这是一个定论,但是要了解这个规则确实需要不少知识。不少书上告诉你==只能比较地址,地址不一样,那就是false,string存在着相同字面量不同地址的问题。知道的多一些的,还知道有常量池,但是什么样的string在常量池呢,本文就详细的说一下各种情况。

string常量池

  大家都知道string能通过==返回true是因为常量池,他们的地址是相同的,在两种情况下我们会用到,第一种是编译阶段,java在编译成字节码的同时,会把常量标志出来,在编译阶段确定的字符串会进入常量池;第二种情况就是string的intern方法,这个方法会把调用的字符串放入常量池(jdk1.7以后中会直接获取堆中string对象的引用,文章后面会具体说),并且返回常量池的地址,如果常量池有相同的字符串,则返回常量池的地址,不再创建。

常见场景

String a = "xxx";
String b = "xxx";

这种情况a==b为true,因为在编译时已经确定了xxx的字符串

String a ="xxx1";
final int  value =1;
String b="xxx"+value;

这种情况下a==b为true,在编译时final的值已经确定,编译器进行了宏替换。

String a="xxx1";
int value =1;
String b="xxx"+value;

这种情况下a==b为flase,在编译是value作为一个变量出现,无法确定value的值,所以b对象的内容不会进入常量池。

String a ="xxx1";
final int value=getNum();// getNum()是根据各种变量计算返回1
String b="xxx"+value;

这种情况下a==b为false,此处的final 修饰的value只是一个不可变的值,在编译时期根本不知道数据是多少,此时编译不会出现宏替换的情况,b对象的内容也不会进入常量池。

String a= "xxx";
String b= new String("xxx");

这种情况下a==b为false,因为b是堆中新建的地址,a的地址在常量池,地址不同。

String a = "xxx";
String b=new String("xxx").intern();

这种情况下a==b为true,a对象的地址在常量池,新建的string虽然在堆,但是执行了intern方法,在运行时,获取到了常量池xxx的地址,并且给b,此时a和b的地址是相同的。

加号的解析

String x ="aa"+"bb";

上面情况常量池有几个String对象,猜测很多,这里就不列举了,最终只有一个对象进入常量池,就是aabb,至于aa,bb在编译时已经做了优化,并不会放入常量池中。

int value =10;
String x ="xxx"+value;

x引用的对象在哪里?堆还是栈(上面已经验证过常量池中没有),实际是在堆中。


javap.png

下面就是用Javap查看的执行过程(sunjdk),String x="xxx"+value;的过程被解析成为Stringbuilder.append(“xxx”),Stringbuilder.append(value),Stringbuilder.toString(),三个步骤。

Stringbuilder.toString()就是新建了一个String对象。

jdk变动

随着jdk的变动,有一些代码执行的结果也就不一样了。1.7开始移动常量池进入堆中了。

              String str = new String(new char[]{'a','b'});
              str.intern();
              String str1 ="ab";

如果在你熟悉API和string常量池的解析后,认为是str==str1为flase的请往下看。
在1.7之前这个代码str和str1是不相等的,但是从1.7开始,这个str和str1地址相等了。在1.7之前,字符串常量池是在方法区中,new出来的对象在堆,调用Intern方法肯定是在字符串常量池中新建了一个对象,但是jvm为了避免字符串常量池那边oom,1.7开始把字符串常量池移入到了堆内存,在常量池没有ab字符串的时候,str.intern()方法会让常量池拿到堆里对象的引用,并没有新建一个对象,所以str1指向的对象就和str指向的对象相同了。这个可以直接在调试的时候查看对象ID来检测,或者使用hsdb来查看内存来验证。下面推荐一篇解析了intern,native代码的博客,帮助理解。http://brokendreams.iteye.com/blog/2260870

常见考题

java为什么不是第一次出现
String  sb = new StringBuilder("ja").append("va").toString();

上面java是我们第一次写代码出现,但是常量池中早已有这个关键字,所以Intern返回的地址是早已进入常量池中的地址。

产生对象个数问题
String  sb = new StringBuilder("hello").toString();

此时产生了2个hello对象,一个在常量池,一个在内存。具体请查看 StringBuilder的构造过程。已经返回对象的过程,下面是stringbuilder构造时调用的一个方法

  public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

代码的最后做了一次char[]数据拷贝。把传入的string转化成char[]保持成成员变量。然后看tostring的过程,就是新建了一个string对象。

    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }
这里说的两个Hello对象,一个是作为StringBuilder的参数传入的(常量池),一个是作为返回值新建的(堆中)。
目录
相关文章
|
7月前
|
存储 安全 Java
36、Java 中的 String、StringBuilder、StringBuffer、字符串常量池和 intern 方法
36、Java 中的 String、StringBuilder、StringBuffer、字符串常量池和 intern 方法
74 0
|
存储 Java 编译器
JDK8中String的intern()方法详细解读【内存图解+多种例子+1.1w字长文】
JDK8中String的intern()方法详细解读【内存图解+多种例子+1.1w字长文】
JDK8中String的intern()方法详细解读【内存图解+多种例子+1.1w字长文】
Java String 的 常量池 和 intern 方法 简析
Java String 的 常量池 和 intern 方法 简析字符串常量池和 intern 方法 先举个例子,我们创建 10000 个相同的 String,并且不使用常量池 String[] list = new String[10000]; for (int i = 0; i < 10000; i++) { list[i] = new String(new char[]{'a','b','c'}); } 结果是:每个 String 都用新开的对象,占用大量内存 现在我们这样创建,增加一个 map ,key 和 value 内容一样,每次取的时候,先检查一下 map 里面有没
|
存储 安全 Java
第36篇:Java 中的 String、StringBuilder、StringBuffer、字符串常量池和 intern 方法
🌿 字符串的底层是 char[],但是 char 数组和字符串不能等价。char 数组是 char 数组,字符串是字符串。 🌿 C 语言中是可以把 char 数组和字符串等价的 ✏️ 所有的 字符串字面量(如:"林哥")都是 String 类的实例 ✏️ String 对象创建完毕后,String 对象的字符内容是不可以修改的 🌿 String 对象的引用变量的指向是可以修改的
127 0
第36篇:Java 中的 String、StringBuilder、StringBuffer、字符串常量池和 intern 方法
|
存储 Java
Java 基础:String——常量池与 intern
Java 基础:String——常量池与 intern
60 0
Java 基础:String——常量池与 intern
|
存储 Java
面试题系列第6篇:JVM字符串常量池及String的intern方法详解?
面试题系列第6篇:JVM字符串常量池及String的intern方法详解?
121 0
面试题系列第6篇:JVM字符串常量池及String的intern方法详解?
|
存储 Java 编译器
|
Java
String类的intern()
JAVA中String类的intern()方法的作用
1594 0
通过反编译看Java String及intern内幕--费元星站长
通过反编译看Java String及intern内幕   一、字符串问题   字符串在我们平时的编码工作中其实用的非常多,并且用起来也比较简单,所以很少有人对其做特别深入的研究。倒是面试或者笔试的时候,往往会涉及比较深入和难度大一点的问题。
944 0