Java常量池详解

 更新时间:2021年09月10日 12:01:33   作者:pluto_blog  
下面小编就为大家带来一篇浅谈java常量池。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

java中有几种不同的常量池,以下的内容是对java中几种常量池的介绍,其中最常见的就是字符串常量池。

(1)class常量池

在Java中,Java类被编译后就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译器生成的各种字面量和符号引用,每个class文件都有一个class常量池。

其中字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;

符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

(2)运行时常量池

运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,是方法区的一部分(JDK1.8 运行时常量池在元空间,元空间也是方法区的一种实现)。不同之处是:它的字面量可以动态的添加(String类的intern()),符号引用可以被解析为直接引用。

JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,这里所说的常量包括:基本类型包装类(包装类不管理浮点型,整形只会管理-128到127)和字符串类型(即通过String.intern()方法可以强制将String放入常量池),运行时常量池是每个类私有的。在解析阶段,会把符号引用替换为直接引用。

(3)基本类型包装类常量池

Java 基本类型的包装类的大部分都实现了常量池技术。Byte,Short,Integer,Long这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean直接返回True或False,如果超出对应范围就会去创建新的对象。两种浮点数类型的包装类Float,Double并没有实现常量池技术。

Integer 缓存源码:

/**
*此方法将始终缓存-128 到 127(包括端点)范围内的值,并可以缓存此范围之外的其他值。
*/
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
      return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
}

举个栗子:

Integer i1 = 40;
  Integer i2 = 40;
  Integer i3 = 0;
  Integer i4 = new Integer(40);
  Integer i5 = new Integer(40);
  Integer i6 = new Integer(0);
  System.out.println("i1=i2   " + (i1 == i2));
  System.out.println("i1=i2+i3   " + (i1 == i2 + i3));
  System.out.println("i1=i4   " + (i1 == i4));
  System.out.println("i4=i5   " + (i4 == i5));
  System.out.println("i4=i5+i6   " + (i4 == i5 + i6));   
  System.out.println("40=i5+i6   " + (40 == i5 + i6));

结果:

i1=i2         true
i1=i2+i3   true
i1=i4        false
i4=i5        false
i4=i5+i6   true
40=i5+i6   true

解释:1-4语句结果应该很显然,因为Integer i1=40 这一行代码会发生装箱,也就是说这行代码等价于 Integer i1=Integer.valueOf(40),Integer.valueOf()方法基于减少对象创建次数和节省内存的考虑,缓存了[-128,127]之间的数字,如果在此数字范围内直接返回缓存中的对象。在此之外,直接new出来,显然40在常量池的缓存[-128,127]范围内;因此,i1 直接使用的是常量池中的对象。而Integer i1 = new Integer(40) 会直接创建新的对象;语句 i4 == i5 + i6,因为+这个操作符不适用于 Integer 对象,首先 i5 和 i6 进行自动拆箱操作,进行数值相加,即 i4 == 40。然后 Integer 对象无法与数值进行直接比较,所以 i4 自动拆箱转为 int 值 40,最终这条语句转为 40 == 40 进行数值比较,所以结果为true。第六条语句同理。

额外说明:所有整型包装类对象之间值的比较,全部使用 equals 方法比较。

对于Integer var = ?在-128至127之间的赋值,Integer对象是在 IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,推荐使用equals方法进行判断。

(4)字符串常量池

在JDK1.6及之前版本,字符串常量池存放在方法区中的,在JDK1.7版本以后,字符串常量池被移到了堆中了。

HotSpot VM里,记录interned string的一个全局表叫做StringTable,它本质上就是个HashSet<String>;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。

注意:它只存储对java.lang.String实例的引用,而不存储String对象的内容

字符串常量池和上面的基本类型包装类常量池有些不同,字符串常量池中没有事先缓存一些数据,而是如果要创建的字符串在常量池内存在就返回对象的引用,如果不存在就创建一个放在常量池中;

在Java中,有两种创建字符串对象的方法,一种是字面量直接创建,另一种是new一个String对象,这两种方法创建字符串对象的过程会不一样;

(1)String str = "abc";
(2)String str = new String("abc");

如果是第一种方式创建对象,因为是字面量直接创建,所以在编译的时候是确定的,如果该字符串不在常量池中会将该字符串放入常量池中并返回字符串对象的引用,如果在常量池中直接返回字符串对象的引用,如果是第二种方式创建对象,因为要创建String类型的对象,String对象是在运行时才加载到内存的堆中的,属于运行时创建,所以要先在堆中创建一个String对象,再去常量池中寻找是否有相同的字符串,如果有就返回堆中Sring对象的引用,如果没有则在将该字符串加入常量池中。

举个栗子:

比较下列两种创建字符串的方法:

String str1 = new String("abc");

String str2 = "abc";

答案:第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。 运行时期创建 。

第二种是先在栈中创建一个对String类的对象引用变量str2,然后通过符号引用去字符串常量池里找有没有”abc”,如果没有,则将”abc”存放进字符串常量池,并令str2指向”abc”,如果已经有”abc” 则直接令str2指向“abc”。“abc”存于常量池在 编译期间完成 。

String s = new String("abc")
这条语句创建了几个对象?

答案:共2个。第一个对象是”abc”字符串存储在常量池中,第二个对象在Java Heap中的 String 对象。这里不要混淆了s是放在栈里面的指向了Heap堆中的String对象。

String s1 = new String("s1") ;

String s1 = new String("s1") ;

上面一共创建了几个对象?

答案:3个 ,编译期常量池中创建1个,运行期堆中创建2个.(用new创建的每new一次就在堆上创建一个对象,用引号创建的如果在常量池中已有就直接指向,不用创建)

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

相关文章

  • springmvc实现简单的拦截器

    springmvc实现简单的拦截器

    这篇文章主要为大家详细介绍了springmvc实现简单拦截器的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • 大厂禁止SpringBoot在项目使用Tomcat容器原理解析

    大厂禁止SpringBoot在项目使用Tomcat容器原理解析

    这篇文章主要为大家介绍了大厂禁止SpringBoot在项目使用Tomcat原理解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • 解决JavaMail附件名字过长导致的乱码问题

    解决JavaMail附件名字过长导致的乱码问题

    这篇文章主要介绍了解决JavaMail附件名字过长导致的乱码问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • 收集的一些常用java正则表达式

    收集的一些常用java正则表达式

    收集的一些常用java正则表达式,需要的朋友可以参考一下
    2013-02-02
  • 深入理解什么是Mybatis懒加载(延迟加载)

    深入理解什么是Mybatis懒加载(延迟加载)

    这篇文章主要介绍了深入理解什么是Mybatis懒加载(延迟加载),mybatis的懒加载,也称为延迟加载,是指在进行关联查询的时候,按照设置延迟规则推迟对关联对象的select查询,延迟加载可以有效的减少数据库压力,需要的朋友可以参考下
    2023-10-10
  • Java使用Random类生成随机数示例

    Java使用Random类生成随机数示例

    这篇文章主要介绍了Java使用Random类生成随机数,结合实例形式分析了java基于Random类生成随机数与遍历输出相关操作技巧,需要的朋友可以参考下
    2019-07-07
  • Java图片处理之获取gif图一帧图片的两种方法

    Java图片处理之获取gif图一帧图片的两种方法

    这篇文章主要给大家介绍了关于Java图片处理之获取gif图一帧图片的两种方法,分别是利用Java原生代码和使用im4java调用ImageMagick来实现,两种方法都给出来示例代码供大家参考学习,需要的朋友们下面来一起看看吧。
    2017-10-10
  • 简单了解java等待唤醒机制原理及使用

    简单了解java等待唤醒机制原理及使用

    这篇文章主要介绍了简单了解java等待唤醒机制原理及使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • SpringBoot中对应2.0.x版本的Redis配置详解

    SpringBoot中对应2.0.x版本的Redis配置详解

    这篇文章主要为大家介绍了SpringBoot中对应2.0.x版本的Redis配置详解,文中的实现步骤讲解详细,感兴趣的小伙伴们可以了解一下
    2022-06-06
  • java EasyExcel面向Excel文档读写逻辑示例详解

    java EasyExcel面向Excel文档读写逻辑示例详解

    这篇文章主要为大家介绍了java EasyExcel面向Excel文档读写逻辑示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07

最新评论