zoukankan      html  css  js  c++  java
  • 彻底弄懂字符串常量池等相关问题

     正文前先来一波福利推荐:

     福利一:

    百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。

    福利二:

    毕业答辩以及工作上各种答辩,平时积累了不少精品PPT,现在共享给大家,大大小小加起来有几千套,总有适合你的一款,很多是网上是下载不到。

    获取方式:

    微信关注 精品3分钟 ,id为 jingpin3mins,关注后回复   百万年薪架构师 ,精品收藏PPT  获取云盘链接,谢谢大家支持!

    -----------------------正文开始---------------------------

    --------------------------------------------------

    正文前先来个福利推荐:

    本人因为毕业答辩以及工作上各种答辩,平时积累了不少精品PPT。现在共享给大家,大大小小加起来有几千套,总有适合你的一款,

    很多是网上是下载不到,为了失效,我把连接设置成了关键词获取,关注后回复 精品收藏PPT 可以收到云盘的链接 ,

    微信关注 精品3分钟 ,id为 jingpin3mins,整理这些资源很不容易,希望领取的小伙伴转发+点好看,谢谢大家支持啦!

    ---------------------------------------------------

    前言:

      在平时我们使用字符串一般就是拿来直接搞起,很少有深入的去想过这方面的知识,导致别人在考我们的时候,会问 String str = new String("123"); 这个一行代码执行创建了几个对象, String str1= str + new String("456");这行代码中str1存储在内存的哪个位置,堆or 字符串常量区(方法区)? 会把我们问的哑口无言了;哈哈哈哈,其实也不是水平问题,是我们平时可以仔细的去总结该类问题,下面就详细的对这类问题进行总结;

    一、首先把容易混淆以及被人问傻的几个问题归类汇总:[没看本文答案解析,全部答对的请留言,我关注你]

    问题1:

       String str1 = new String("1");
        str1.intern();
        String str2 = "1";
        System.out.println(str1 == str2);  //结果是 false or true?
    
        String str3 = new String("2") + new String("2");
        t3.intern();
        String str4 = "22";
        System.out.println(str3 == str4); //结果是 false or true?

    问题2:

     String str1 = "aaa";
      String str2 = "bbb";
      String str3 = "aaabbb";
      String str4 = str1 + str2;
      String str5 = "aaa" + "bbb";
      System.out.println(str3 == str4); // false or true
      System.out.println(str3 == str4.intern()); // true or false
      System.out.println(str3 == str5);// true or false

    问题3:

    String t1 = new String("2");
    String t2 = "2";
    t1.intern();
    System.out.println(t1 == t2); //false or true
    
    String t3 = new String("2") + new String("2");
    String t4 = "22";
    t3.intern();
    System.out.println(t3 == t4); //false or true

    问题4:

        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
    
        System.out.println(c == d);
        System.out.Println(e == f);
        System.out.println(c == (a + b));
        System.out.println(c.equals(a+b));
        System.out.println(g == (a + b));
        System.out.println(g.equals(a + b));

    二、知识储备

    在解答这四个问题的过程中,我们首先说一下几个知识,很重要:

    1.intern()函数

      intern函数的作用是将对应的符号常量进入特殊处理,在1.6以前 和 1.7以后有不同的处理;

      先看1.6:

          在1.6中,intern的处理是 先判断字符串常量是否在字符串常量池中,如果存在直接返回该常量,如果没有找到,则将该字符串常量加入到字符串常量区,也就是在字符串常量区建立该常量;

      在1.7中:

          在1.7中,intern的处理是 先判断字符串常量是否在字符串常量池中,如果存在直接返回该常量,如果没有找到,说明该字符串常量在堆中,则处理是把堆区该对象的引用加入到字符串常量池中,以后别人拿到的是该字符串常量的引用,实际存在堆中;【这里感谢以为网友的纠正,一开始理解为在堆区建立该字符串对象在添加引用了,其实调用该方法的字符串对象要么在堆区要么在常量池中的】

    2.常量池的分类【理解即可】

    2.1 class文件常量池

    在Class文件中除了有类的版本【高版本可以加载低版本】、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table)【此时没有加载进内存,也就是在文件中】,用于存放编译期生成的各种字面量和符号引用

    下面对字面量和符号引用进行说明
    字面量
    字面量类似与我们平常说的常量,主要包括:

    1. 文本字符串:就是我们在代码中能够看到的字符串,例如String a = “aa”。其中”aa”就是字面量。
    2. 被final修饰的变量。

    符号引用
    主要包括以下常量:

    1. 类和接口和全限定名:例如对于String这个类,它的全限定名就是java/lang/String。
    2. 字段的名称和描述符:所谓字段就是类或者接口中声明的变量,包括类级别变量(static)和实例级的变量。
    3. 方法的名称和描述符。所谓描述符就相当于方法的参数类型+返回值类型

    2.2 运行时常量池

    我们知道类加载器会加载对应的Class文件,而上面的class文件中的常量池,会在类加载后进入方法区中的运行时常量池【此时存在在内存中】。并且需要的注意的是,运行时常量池是全局共享的,多个类共用一个运行时常量池。并且class文件中常量池多个相同的字符串在运行时常量池只会存在一份。
    注意运行时常量池存在于方法区中。

    2.3 字符串常量池

      看名字我们就可以知道字符串常量池会用来存放字符串,也就是说常量池中的文本字符串会在类加载时进入字符串常量池。
    那字符串常量池和运行时常量池是什么关系呢?上面我们说常量池中的字面量会在类加载后进入运行时常量池,其中字面量中有包括文本字符串,显然从这段文字我们可以知道字符串常量池存在于运行时常量池中。也就存在于方法区中。
    不过在周志明那本深入java虚拟机中有说到,到了JDK1.7时,字符串常量池就被移出了方法区,转移到了里了。
    那么我们可以推断,到了JDK1.7以及之后的版本中,运行时常量池并没有包含字符串常量池,运行时常量池存在于方法区中,而字符串常量池存在于中。

    3.问题解析【重点】

    3.1 问题1解析

      tring str1 = new String("1");
      解析:首先此行代码创建了两个对象,在执行前会在常量池中创建一个"1"的对象,然后执行该行代码时new一个"1"的对象存放在堆区中;然后str1指向堆区中的对象;
    str1.intern();
      解析:该行代码首先查看"1"字符串有没有存在在常量池中,此时存在则直接返回该常量,这里返回后没有引用接受他,【假如不存在的话在 jdk1.6中会在常量池中建立该常量,在jdk1.7以后会把堆中该对象的引用放在常量池中】
    String str2 = "1";
      解析:此时"1"已经存在在常量池中,str2指向常量池中的对象;

    System.out.println(str1 == str2); //结果是 false or true?
      解析:str1指向堆区的对象,str2指向常量池中的对象,两个引用指向的地址不同,输入false; String str3 = new String("2") + new String("2");
      解析:此行代码执行的底层执行过程是 首先使用StringBuffer的append方法将"2"和"2"拼接在一块,然后调用toString方法new出“22”;所以此时的“22”字符串是创建在堆区的;
    t3.intern();
      解析:此行代码执行时字符串常量池中没有"22",所以此时在jdk1.6中会在字符串常量池中创建"22",而在jdk1.7
    以后会把堆中该对象的引用放在常量池中;
    
        String str4 = "22";
      解析:此时的str4在jdk1.6中会指向方法区,而在jdk1,7中会指向堆区;
    System.out.println(str3 == str4); //结果是 false or true?
      解析:很明显了 jdk1.6中为false 在jdk1.7中为true;

    3.2 问题2解析

     String str1 = "aaa";
     解析:str1指向方法区;
    String str2 = "bbb";
     解析: str2 指向方法区
    String str3 = "aaabbb";
    解析:str3指向方法区
    String str4 = str1 + str2;
    解析:此行代码上边已经说过原理。str4指向堆区
    String str5 = "aaa" + "bbb";
    解析:该行代码重点说明一下,jvm对其有优化处理,也就是在编译阶段就会将这两个字符串常量进行拼接,也就是"aaabbb";所以他是在方法区中的;’
    System.out.println(str3 == str4); // false or true
     解析:很明显 为false, 一个指向堆 一个指向方法区
    System.out.println(str3 == str4.intern()); // true or false
    解析:jdk1.6中str4.intern会把“aaabbb”放在方法区,1.7后在堆区,所以在1.6中会是true 但是在1.7中是false
    System.out.println(str3 == str5);// true or false
    解析:都指向字符串常量区,字符串长常量区在方法区,相同的字符串只存在一份,其实这个地方在扩展一下,因为方法区的字符串常量是共享的,在两个线程同时共享这个字符串时,如果一个线程改变他会是怎么样的呢,其实这种场景下是线程安全的,jvm会将改变后的字符串常量在
      字符串常量池中重新创建一个处理,可以保证线程安全

    3.3 问题3解析

    tring t1 = new String("2");
    解析:创建了两个对象,t1指向堆区
    String t2 = "2";
    解析:t2指向字符串常量池
    t1.intern();
    解析:字符串常量池已经存在该字符串,直接返回;
    System.out.println(t1 == t2); //false or true 解析:很明显 false
    String t3 = new String("2") + new String("2");
    解析:过程同问题1 t3指向堆区
    String t4 = "22";
    解析:t4 在1.6 和 1.7中指向不同 t3.intern();
    解析: 字符串常量池中已经存在该字符串 直接返回
    System.out.println(t3 == t4); //false or true
    解析: 很明显为 false 指向不同的内存区

    3.4 问题4解析

    这个地方存在一个知识点。可能是个盲区,这次要彻底记住“

    (1). 内存中有一个java基本类型封装类的常量池。这些类包括Byte, Short, Integer, Long, Character, Boolean。需要注意的是,Float和Double这两个类并没有对应的常量池。


    (2).上面5种整型的包装类的对象是存在范围限定的;范围在-128~127存在在常量池,范围以外则在堆区进行分配。


    (3). 在周志明的那本虚拟机中有这样一句话:包装类的
    “==”运行符在不遇到算术运算的情况下不会自动拆箱,以及他们的equals()方法不处理数据类型的关系,通俗的讲也就是 “==”两边如果有算术运算, 那么自动拆箱和进行数据类型转换处理,比较的是数值等不等能。


    (4).Long的equals方法会先判断是否是Long类型。


    (5).无论是Integer还是Long,他们的equals方法比较的是数值。


    System.out.println(c == d)。
    解析:由于常量池的作用,c与d指向的是同一个对象(注意此时的==比较的是对象,也就是地址,而不是数值)。因此为true


    System.out.println(e == f)。
    由于321超过了127,因此常量池失去了作用,所以e和f数值虽然相同,但不是同一个对象,以此为false。


    System.out.println(c == (a+b))。
    此时==两边有算术运算,会进行拆箱,因此此时比较的是数值,而并非对象。因此为true。


    System.out.println(c.equals(a+b))
    c与a+b的数值相等,为true。


    System.out.pirnln(g == (a + b))
    由于==两边有算术运算,所以比较的是数值,因此为true。


    System.out.println(g.equals(a+b))。
    Long类型的equal在比较是时候,会先判断a+b是否为Long类型,显然a+b不是,因此false

     
  • 相关阅读:
    TomCat 的 Jenkins 报错:反向代理设置有误
    【Django】如何在类视图、普通视图单独不做CSRF校验
    【Django】HTML如何显示富文本内容
    Djaong 运行报错:ValueError: Unable to configure handler 'default'
    Windows 环境使用 Xshell 连接 VMware 虚拟机上的 CentOS 系统
    【我的青春我做主】让自己的心境安宁
    Django 使用 Nginx + uWSGI 启动
    Django_文件下载
    追梦何须要问成败,只管向前吧
    PyCharm:设置py文件头部信息
  • 原文地址:https://www.cnblogs.com/gxyandwmm/p/9495923.html
Copyright © 2011-2022 走看看