java-String常量池的知识点你知道多少?-结合jdk版本变更

目录

1.String常量池位置在哪?

2.String常量池里存的是对象还是引用?

3.String类中的==比较以及intern()方法原理?


回:1.String常量池位置在哪?

在jdk6版本,String常量池是在方法区中的运行时常量池。

 

三种情况:

java7之前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变;例如 :PermSize 和 -XX:MaxPermSize

 

java7中,存储在永久代的部分数据就已经转移到Java Heap或者Native memory。但永久代仍存在于JDK 1.7中,并没有完全移除,譬如符号引用(Symbols)转移到了native memory;字符串常量池(interned strings)转移到了Java heap;类的静态变量(class statics)转移到了Java heap。

 

java8中,取消永久代, 其余的数据作为元数据存储在元空间中存放于元空间(Metaspace), 元空间是一块与堆不相连的本地内存。( 元数据是数据的数据或者叫做用来描述数据的数据或者叫做信息的信息。(比如原本方法区存储的类信息、即时编译器编译后的代码等),也可以把元数据简单的理解成,最小的数据单位。元数据可以为数据说明其元素或属性(名称、大小、数据类型、等),或其结构(长度、字段、数据列),或其相关数据(位于何处、如何联系、拥有者)。)

 

那方法区,永久代,元空间这三者之间又是怎样的逻辑关系?

首先方法区是java虚拟机规范中定义的区域具体如何实现没有规定,每个虚拟机可以自由实现。

 拿hotspot实现来说,在jdk7、8之前会在虚拟机内存中划分一块区域用于存储编译后的类字节码信息、类的静态变量等,称之为永久代,作为方法区的实现。

从jdk8开始,已经移除了永久代的实现,但是方法区还在,原本永久代中储存的类的元数据将储存在元空间中,而类的静态变量和常量池放在java堆中,元空间是对方法区的一种实现,只不过他使用的不是虚拟机内存而是本地内存

 

 

 

回:2.String常量池里存的是对象还是引用?

答案:String常量池存的是对象

直接查看API:

 

翻译一下,String类的intern()方法:一个初始为空的字符串池,它由类String独自维护。当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。 对于任意两个字符串s和t,当且仅当s.equals(t)为true时,s.intern() == t.intern()才为true。所有字面值字符串和字符串赋值常量表达式都使用 intern方法进行操作。

 

 

 

回:3.String类中的==比较以及intern()方法原理?

a. 首先==比较的是对象的引用地址,equals()比较的是该对象的值

 

b. jdk1.5后自动装箱和拆箱功能

 

1. 目前实现常量池技术的有Integer、Byte、Short、Long、Character、Boolean

 

2. 代码实例:

   Integer i= new Integer(3);
          Integer i1=3;
          int i2= 3;
 
          System.out.println(i==i2);//true  自动拆箱 为int
          System.out.println(i==i1);//false 自动装箱 为 new Integer()
          Integer f1=100,f2=100,f3=200,f4=200;
          System.out.println(f1==f2);//true
          System.out.println(f3==f4);//false
Integer i= new Integer(3); Integer i1=3; int i2= 3; System.out.println(i==i2);//true 自动拆箱 为int System.out.println(i==i1);//false 自动装箱 为 new Integer() Integer f1=100,f2=100,f3=200,f4=200; System.out.println(f1==f2);//true System.out.println(f3==f4);//false

 

当创建Integer对象时,不使用new Integer(int i)语句,大小在-128~127之间,对象存放在Integer常量池中。

例如:Integer  a = 10;

调用的是Integer.valueOf()方法,代码为

 

  public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

 这也是自动装箱的代码实现。

总结:Integer和int ==比较,自动拆箱,new Integer()和Integer比较,自动装箱

 

c. String类中intern方法

 

1.为什么要介绍intern()方法

intern()方法设计的初衷,就是重用String对象,以节省内存消耗

使用了intern()方法后程序运行时间有所增加。这是因为程序中每次都是用了new String后又进行intern()操作的耗时时间,但是不使用intern()占用内存空间导致GC的时间是要远远大于这点时间的。

 

2.  深入认识intern()方法

JDK1.7后,常量池被放入到堆空间中,导致intern()函数的功能不同,下面代码以jdk1.8为例分析:

1.字面量创建对象方式:

String s1 ="abc";//常量池中添加“abc”对象,返回引用地址给s1对象
String s2="abc";//通过equals()方法判断常量池中已有值为abc的对象,返回相同的引用
System.out.println(s1==s2);//true  所以s1==s2

3.new 创建对象的方式:

String s3 = new String("def");//在常量池中添加“def”对象,在堆中创建值为“def”的对象s3,返回指向堆中s3的引用
String s4 = new String("def");//常量池中已有值为“def”的对象,不做处理,在堆中创建值为“def”的对象s4,返回指向堆中s4的引用
System.out.println(s3==s4);//false   堆中引用地址不同,所以为false

4.intern()涉入的比较

String s = new String("1");  //常量池中添加“1”,生成堆对象s,返回指向堆中s的引用
String intern = s.intern();  //常量池中已有“1”,返回常量池中“1”的引用
String s2 = "1";  //常量池中已有“1”,返回常量池中“1”的引用
System.out.println(s2 == intern);//true  两者返回的都是常量池中“1”的引用
System.out.println(s == intern);//false  堆引用地址和常量池引用地址不相等

5.两个String类型对象相加

代码一:

String s3 = new String("1") + new String("1");//看第一行解释
String intern = s3.intern(); //常量池中没有“11”,copy堆中s3引用添加到常量池
String s4 = "11";  //这一行代码会直接去常量池中创建,但是发现已经有这个对象了,返回 s3对象的引用。
System.out.println(s3 == s4);//true  都是指向堆中的引用地址

解释:第一行new 相加,在常量池中生成“1”对象,在堆中创建值为“11”的s3对象,并返回指向堆中的s3的引用,其实这一行代码运行时是这样的String s6=new StringBuilder().append(new String("1")).append(new String("1")).toString();这里的过程是通过StringBuilder这个类实现的,我们来看一下StringBuilder类中的toString()的源码:

它是通过new String()的方式来作为值进行返回的,所以是在堆中开辟的一块空间。并未在常量池中创建“11”对象。

 

 

代码二:

String s1="1";
String s2="1";
String name=s1+s2;
String intern = name.intern();
System.out.println(intern==name);//true

String name1="11";
System.out.println(name==name1);//true

 

原理与代码一相同

 

6.两个字面量相加

String s1="abc1";//此处是数字1
String s2="abc"+1;
System.out.println(s1==s2);// 第一次比较
String s3="ab";
String s4="c";
String s5="abc";
String s6=s3+s4;
System.out.println(s5==s6);// 第二次比较

 

解释:第一次比较的那里,因为字符串abc和数字1都是字面量,所以加起来还是个字面量,又因为常量池中已经有了s1指向的字面量abc1,所以s2也是指向了字面量abc1

 

7.final类型的String

 

特例1:
public class test {
public static final String s1="abc";
public static final String s2="def";
public static void main(String[] args) {
String s3=s1+s2;
String s4="abcdef";
System.out.println(s3==s4);//true
}
}

解释: 因为s1和s2都是final类型的且在编译阶段都是已经复制了,所以相当于一个常量,当执行Strings3=s1+s2;的时候,s3已经是字符串abcdef了,所以相等。

 

特例2:
public class test {
public static final String s1;
public static final String s2;
static{
s1="abc";
s2="def";
}
public static void main(String[] args) {
String s3=s1+s2;
String s4="abcdef";
System.out.println(s3==s4);//false
}
}

解释:虽然s1和s2都是final类型的但是一开始没有初始化,在编译期还不可以知道具体的值,还是变量,所以什么时候赋值,赋什么值都是个变数。所以是false。

 

8.StringBuilder()方法

String s1 = new StringBuilder().append("aa").append("bb").toString();
System.out.println(s1.intern() == s1);//true
       
String s2 = new StringBuilder().append("ja").append("va").toString();
System.out.println(s2.intern() == s2); //false

解释:第一个比较,通过字符串拼接在堆内存中生成值为“aabb”的对象,返回s1对象的引用但并未在常量池中生成“aabb”对象,s1.intern()在堆中copy一份引用地址对象到常量池,所以true

第二个比较,因为“java”在常量池中本身就存在,所以在堆中生成“java”的对象,返回s2的引用地址,s2.intern()去常量池中检查到(equals()判断)该对象存在,所以返回常量池中对象的引用地址。所以为false

 

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值