JAVA常量池,一篇文章就足够入门了。(含图解)

前言

一直在《深入理解JVM》对常量池只有一个浅薄的了解,之前也遇到过这种题目,今天还是要挑出来进行一次全方位的了解。

常量池分类

常量池大体可以分为:静态常量池,运行时常量池。

  • 静态常量池 存在于class文件中,比如经常使用的javap -verbose中,常量池总是在最前面把?

  • 运行时常量池呢,就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池 值的是运行时常量池。所以呢,讨论的都是运行时常量池

字符串常量池

最最最流行的、最典型的就是字符串了

典型范例:

String a = "abc";
String b = new String("abc");
System.out.println(a == b);

----*----
结果:false

这里写图片描述
这个是第一个需要理解的地方,a指向哪片内存,b又指向哪片内存呢?对象储存在堆中,这个是不用质疑的,而a作为字面量一开始储存在了class文件中,之后运行期,转存至方法区中。它们两个就不是同一个地方存储的。知道了它之后我们就可以通过实例直接进一步了解了

实例

    String s1 = "Hello";
    String s2 = "Hello";
    String s3 = "Hel" + "lo";
    String s4 = "Hel" + new String("lo");
    String s5 = new String("Hello");
    String s6 = s5.intern();
    String s7 = "H";
    String s8 = "ello";
    String s9 = s7 + s8;

    System.out.println(s1 == s2);  // true
    System.out.println(s1 == s3);  // true
    System.out.println(s1 == s4);  // false
    System.out.println(s1 == s9);  // false
    System.out.println(s4 == s5);  // false
    System.out.println(s1 == s6);  // true

分析:
1、s1 = = s2 很容易可以判断出来。s1 和 s2 都指向了方法区常量池中的Hello。
2、s1 = = s3 这里要注意一下,因为做+号的时候,会进行优化,自动生成Hello赋值给s3,所以也是true
3、s1 = = s4 s4是分别用了常量池中的字符串和存放对象的堆中的字符串,做+的时候会进行动态调用,最后生成的仍然是一个String对象存放在堆中。
这里写图片描述
4、s1 = = s9 在JAVA9中,因为用的是动态调用,所以返回的是一个新的String对象。所以s9和s4,s5这三者都不是指向同一块内存
这里写图片描述
5、s1 = = s6 为啥s1 和 s6地址相等呢? 归功于intern方法,这个方法首先在常量池中查找是否存在一份equal相等的字符串如果有的话就返回该字符串的引用,没有的话就将它加入到字符串常量池中,所以存在于class中的常量池并非固定不变的,可以用intern方法加入新的

需要注意的特例

1、常量拼接

    public static final String a = "123";
    public static final String b = "456";

    public static void main(String[] args)
    {
        String c = "123456";
        String d = a + b;
        System.out.println(c == d);
    }

------反编译结果-------
         0: ldc           #2                  // String 123456
         2: astore_1
         3: ldc           #2                  // String 123456
         5: astore_2
         6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;

我们可以发现,对于final类型的常量它们已经在编译中被确定下来,自动执行了+号,把它们拼接了起来,所以就相当于直接”123” + “456”;

2、static 静态代码块

    public static final String a;
    public static final String b;

    static {
        a = "123";
        b = "456";
    }

    public static void main(String[] args)
    {
        String c = "123456";
        String d = a + b;
        System.out.println(c == d);
    }

------反编译结果-------
         3: getstatic     #3                  // Field a:Ljava/lang/String;
         6: getstatic     #4                  // Field b:Ljava/lang/String;
         9: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

上个例子是在编译期间,就已经确定了a和b,但是在这段代码中,编译期static不执行的,a和b的值是未知的,static代码块,在初始化的时候被执行,初始化属于类加载的一部分,属于运行期。看看反编译的结果,很明显使用的是indy指令,动态调用返回String类型对象。一个在堆中一个在方法区常量池中,自然是不一样的。

包装类的常量池技术(缓存)

简单介绍

相信学过java的同学都知道自动装箱和自动拆箱,自动装箱常见的就是valueOf这个方法,自动拆箱就是intValue方法。在它们的源码中有一段神秘的代码值得我们好好看看。除了两个包装类Long和Double 没有实现这个缓存技术,其它的包装类均实现了它。

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[];

static {
    // high value may be configured by property
    int h = 127;
    String integerCacheHighPropValue =
        VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    if (integerCacheHighPropValue != null) {
        try {
            int i = parseInt(integerCacheHighPropValue);
            i = Math.max(i, 127);
            // Maximum array size is Integer.MAX_VALUE
            h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
        } catch( NumberFormatException nfe) {
            // If the property cannot be parsed into an int, ignore it.
        }
    }
    high = h;

    cache = new Integer[(high - low) + 1];
    int j = low;
    for(int k = 0; k < cache.length; k++)
        cache[k] = new Integer(j++);

    // range [-128, 127] must be interned (JLS7 5.1.7)
    assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

分析:我们可以看到从-128~127的数全部被自动加入到了常量池里面,意味着这个段的数使用的常量值的地址都是一样的。一个简单的实例

Integer i1 = 40;
Integer i2 = 40;
Double i3 = 40.0Double i4 = 40.0;

System.out.println("i1=i2   " + (i1 == i2));
System.out.println("i3=i4   " + (i3 == i4));

-----结果----
true
false

原理如下:
1、== 这个运算在不出现算数运算符的情况下 不会自动拆箱,所以i1 和 i 2它们不是数值进行的比较,仍然是比较地址是否指向同一块内存

2、它们都在常量池中存储着,类似于这样
这里写图片描述

3、编译阶段已经将代码转变成了调用valueOf方法,使用的是常量池,如果超过了范围则创建新的对象

 2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

复杂实例[-128~127]

  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));

----结果----
(1)i1=i2   true
(2)i1=i2+i3   true
(3)i1=i4   false
(4)i4=i5   false
(5)i4=i5+i6   true
(6)40=i5+i6   true

它们的内存分布大概如下
这里写图片描述
注意点
1、当出现运算符的时候,Integer不可能直接用来运算,所以会进行一次拆箱成为基本数字进行比较

2、==这个符号,既可以比较普通基本类型,也可以比较内存地址看比较的是什么了

分析:
(1)号成立不用多说
(2)号成立是因为运算符自动拆箱
(3)(4)号是因为内存地址不同
(5)(6)号都是自动拆箱的结果

PS:equals方法比较的时候不会处理数据之间的转型,比如Double类型和Integer类型。

超过范围

假设一下,如果超出了这个范围之后呢?正如前文所言,所有的都将成为新的对象

  Integer i1 = 400;
  Integer i2 = 400;
  Integer i3 = 0;
  Integer i4 = new Integer(400);
  Integer i5 = new Integer(400);
  Integer i6 = new Integer(0);
  Integer i7 = 1;
  Integer i8 = 2;
  Integer i9 = 3;

  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("400=i5+i6   " + (400 == i5 + i6));


----结果----
i1=i2   false
i1=i2+i3   true
i1=i4   false
i4=i5   false
i4=i5+i6   true
400=i5+i6   true

总结

关于常量池部分的总结到这里,通过实际的例子和绘图来熟悉了下字符串常量池和包装类的常量池的使用。其中还包括了装箱和拆箱的小知识。收获还是丰厚的,终于明白了常量池的内容了。~happy-。-,如有笔误,还望纠正

### 回答1: Oracle是一款非常流行的关系型数据库管理系统,对于初学者来说,学习Oracle可能会感到比较困难。因此,很多人会寻找Oracle基础入门教程来帮助自己更好地学习。本文将介绍一些Oracle基础入门教程图解的内容。 首先,Oracle数据库系统的基础结构包括实例和数据库。实例是指一个运行中的Oracle进程,它负责管理和处理数据库中的事务,而数据库则是其中存储的数据。在Oracle教程图解中,通常会展示实例和数据库之间的关系,帮助学习者理解两者之间的联系。 其次,Oracle语言中的数据类型和语法是初学者最需要掌握的内容之一。Oracle教程图解通常会提供一些数据类型的可视化示例,如数字、字符串、日期等,帮助学习者更直观地理解数据类型的概念。另外,Oracle语法也是初学者必须掌握的内容,Oracle教程图解通常会以图形化的方式展示Oracle语法的结构和关键字,让学习者更好地理解Oracle语言的逻辑框架。 最后,Oracle数据库的操作是Oracle基础入门教程图解不可或缺的部分。Oracle教程图解通常会从数据库的创建、表的建立、数据的插入、查询和修改等方面进行讲解,以图解的方式展示操作的步骤和注意事项。这些操作的图解化可以让学习者更好地理解Oracle数据库的操作方法,避免出现一些常见的错误。 总之,Oracle基础入门教程图解是一项非常有价值的学习资源,对于初学者来说尤其如此。通过图解方式,学习者可以更直观、实际地了解Oracle数据库系统的结构和语言,从而更好地掌握Oracle数据库系统的操作技巧。 ### 回答2: Oracle是世界上最大的数据库管理系统之一,得益于其广泛用途和功能强大的特性而成为业界标准。由于其多方面的应用,越来越多的人开始学习,但是,Oracle学习门槛比较高,需要掌握许多细节。因此,Oracle基础入门教程图解是一个非常好的学习资料,逐步向读者介绍Oracle的基本概念、架构、实用工具和基于SQL的基础知识。 首先,文章介绍了Oracle数据库的概念和理念,以及它的架构和对象。然后,它详细介绍了Oracle的常见工具和各种命令,以帮助学习者了解如何使用Oracle的各种工具和SQL语言进行基本管理和查询,如创建表、插入数据、更新记录、删除记录等。 另外,Oracle基础入门教程图解还介绍了Oracle数据库的性能优化技巧,这是Oracle的一个非常重要的方面,需要掌握的知识点非常多。为了使读者更好的理解,本文通过图形化方式介绍了Oracle的慢查询优化和索引相关的知识,帮助读者理解Oracle的复杂性。 总的来说,Oracle基础入门教程图解是一个很好的Oracle学习资料。它的讲解由浅入深、内容丰富、实用性强,能够帮助那些想要了解Oracle数据库管理系统的人快速学习,以及为那些工作需要基本SQL编程技能的程序员提供了更为便捷的步骤入门。 ### 回答3: Oracle是一种完整的企业级数据库应用程序。学习Oracle的基础入门知识对于想要进一步深入学习数据库管理和数据处理的人来说是非常重要的。在教学过程中,图解和实例非常有帮助。 首先,Oracle体系结构是Oracle数据库的基础。它包了三个主要的组件:物理存储结构、逻辑存储结构和实例。物理存储结构指的是实际存储在硬盘上的数据库。逻辑存储结构是根据业务逻辑组织数据的方法。实例是Oracle数据库的运行环境,它包括了应用程序、用户、数据、系统进程和内存缓存。 其次,Oracle数据库的基础概念包括表、字段、行、主键和外键等。表是指将数据组织成行和列来存储的基本数据库对象。字段是表中的每一列。行是表中的每一行。主键是一种约束,用于定义表中的唯一标识符。外键是一种参照完整性约束,用于确保关联表格之间的数据完整性。 第三,Oracle SQL是执行各种数据库操作的语言,如插入、更新以及选择操作。学习Oracle SQL的基础知识之后,需要了解如何使用Oracle的命令行工具以及各种编程接口来与数据库进行交互。实践中编写简单的SQL语句,并通过它们来执行各种查询和操作是非常有帮助的。 综上所述,学习Oracle基础知识需要加深对Oracle体系结构、基本概念以及SQL语言的理解。图解和实例也是学习过程中不可或缺的元素,可以帮助学生更好的理解和掌握相关知识。
评论 50
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

河海哥yyds

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值