字符串常量池深入解析[通俗易懂]

字符串常量池深入解析[通俗易懂]字符串常量池字符串常量池概述一、Java中两种创建字符串对象的方式的分析。二、Intern的实现原理(JDK1.8)三、JDK1.7的Intern的执行四:几种特殊的情况的代码参考的部分文章概述  在分析字符串常量池之前,先来分析一下java的内存区域,然后再各种的情况分析一下各种情况下的情况;在《深入理解java虚拟机》这本书上是这样写的:对于H…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

字符串常量池

概述

在分析字符串常量池之前,先来分析一下java的内存区域,然后再各种的情况分析一下各种情况下的情况;

mark

在《深入理解java虚拟机》这本书上是这样写的:对于HotSpot虚拟机,根据官方发布的路线图信息,现在也有放弃永久代并逐步的改为采用Native Memory来实现方法区的规划了,在目前已经发布的JDK1.7的HotSpot中,已经把原来存放在方法区中的字符串常量池移出。根据查阅的资料显示在JDK1.7以后的版本中字符串常量池移到堆内存区域;同时在jdk1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域

在 JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。

8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:

  • 直接使用双引号声明出来的String对象会直接存储在常量池中。
  • 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中

一、Java中两种创建字符串对象的方式的分析。

String s1 = "abc";
String s2 = "abc";
System.out.println(s1==s2);

结果是 true;

采用字面值的方式创建一个字符串时,JVM首先会去字符串池中查找是否存在”abc”这个对象,如果不存在,则在字符串常量池中创建”abc”这个对象,然后将池中”abc”这个对象的引用地址返回给”abc”对象的引用s1,这样s1会指向字符串常量池中”abc”这个字符串对象;如果存在,则不创建任何对象,直接将池中”abc”这个对象的地址返回,赋给引用s2。因为s1、s2都是指向同一个字符串池中的”abc”对象,所以结果为true。

String s3 = new String("xyz");
String s4 = new String("xyz");
System.out.println(s3==s4);

结果是 false

采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有”xyz”这个字符串对象,如果有,则不在池中再去创建”xyz”这个对象了,直接在堆中创建一个”xyz”字符串对象,然后将堆中的这个”xyz”对象的地址返回赋给引用s3,这样,s3就指向了堆中创建的这个”xyz”字符串对象;如果没有,则首先在字符串池中创建一个”xyz”字符串对象,然后再在堆中创建一个”xyz”字符串对象,然后将堆中这个”xyz”字符串对象的地址返回赋给s3引用,这样,s3指向了堆中创建的这个”xyz”字符串对象。s4则指向了堆中创建的另一个”xyz”字符串对象。s3 、s4是两个指向不同对象的引用,结果当然是false。

二、Intern的实现原理(JDK1.8)

先看一下Intern的源码

/** * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class {@code String}. * <p> * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. * <p> * It follows that for any two strings {@code s} and {@code t}, * {@code s.intern() == t.intern()} is {@code true} * if and only if {@code s.equals(t)} is {@code true}. * <p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java&trade; Language Specification</cite>. * * @return a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. */
    public native String intern();

String#intern方法中看到,这个方法是一个 native 的方法,但注释写的非常明了。“ 当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。

三、JDK1.7的Intern的执行

来看一段代码:

public static void main(String[] args) { 
   
     String s1 = new String("1");
     String s3 = s1.intern();
     String s2 = "1";
     System.out.println(s1 == s2);
     System.out.println(s2 == s3);
     System.out.println(s1 == s3);
    //第二种情况
    String s3 = new String("1") + new String("1");
    String s5 = s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

首先分析一下这个代码:

依据上面的说法,String s = new String("1");这行代码做的事情是;JVM首先在字符串池中查找有没有”1″这个字符串对象,可知在字符串常量池中没有“1”,则首先在字符串池中创建一个”1″字符串对象,然后再在堆中创建一个”1″字符串对象,然后将堆中这个”1″字符串对象的地址返回赋给s1引用,这样,s1指向了堆中创建的这个”1″字符串对象;String s3 = s1.intern();先来看一下JDk1.7及其之后的情况,首先会去字符串常量池中检查池是否包含一个等于“1”的字符串,因为在字符串常量池中已经有字符串“1”对象,所以此时s3指向的是方法区中的对象;String s2 = "1";这句代码首先会去字符串的常量池中查否包含一个等于“1”的字符串,因为字符串常量池中已经存在方法区中了,所以s2指向的是字符串常量池中的对象。

答案就非常好理解了s1 == s2为false ,s2 == s3为true ,s1 == s3为false;

下面分析一下下面的第二种情况:

String s3 = new String("1") + new String("1");首先分析一下这句代码的执行,第一个new String("1")会在字符串常量池和堆内存中创建两个对象,第二个new String("1")此时因为在方法区中已经存在了字符串“1”对象,所以不会再方法区中创建,但是仍然会在堆内存中创建字符串“1”对象。但是new String("1") + new String("1")会在堆中创建一个对象(假设名称为obj),对象的内容为”11″,s3指向新创建的obj对象;s3.intern();这行代码在JDK1.6及其以前的版本中的执行情况是,首先会去字符串常量池中寻找是否已经包含一个等于此String对象(“11”)的字符串(用equals(oject)方法确定),因为在常量池中没有,将 s3中的“11”字符串放入 String 常量池中 ,在常量池中生成一个 “11” 的对象 ;关键点是 jdk7常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的 ;String s4 = "11";这句代码会在字符串常量池中找”11″对象,因为前面intern()方法执行过了,所以此时返回的是s3引用对象的的引用,也就是”11″对象的地址;所以此时s3 == s4为true

关于这一段代码我这里有一个疑问,如果有看到这篇文章的小伙伴希望你能帮我解答一下:

@Test
public void test4(){ 
    
    String s3 = new String("1") + new String("1");
    String s5 = s3.intern();
    String s4 = "11";
    System.out.println(s5 == s3);
    System.out.println(s5 == s4);
    System.out.println(s3 == s4);

    System.out.println("======================");

    String s6 = new String("go") +new String("od");
    String s7 = s6.intern();
    String s8 = "good";
    System.out.println(s6 == s7);
    System.out.println(s7 == s8);
    System.out.println(s6 == s8);
}

这段代码的运行结果是:

false
true
false

关于为什么会出现这样的结果,现在我是没办法解释的通的;随后我修改了字符串“1”,把字符串改为字符串“2”出现的结果是true、true、true;这就更加令我想不通是为什么呢?如果有小伙伴看到,希望能告诉我一下,非常的感谢!(hhriver0601@163.com)

四:几种特殊的情况的代码

先来看一下这种情况的代码:

1:代码中含有StringBuffer的

public void test5(){ 
   
        // Create three strings in three different ways.
        String s1 = "Hello";
        String s2 = new StringBuffer("He").append("llo").toString();
        String s3 = s2.intern();
        // Determine which strings are equivalent using the ==
        // operator
        System.out.println("s1 == s2? " + (s1 == s2));
        System.out.println("s1 == s3? " + (s1 == s3));
        System.out.println();
    }

先分析一下代码:

String s1 = "Hello";和上面分析的一样,在字符串常量池中创建“Hello”对象,然后返回字符串常量池中“Hello”对象的引用。String s2 = new StringBuffer("He").append("llo").toString();这句代码会在字符串常量池中创建“He”对象和“llo”对象,(其实像“a”+”b”这样的代码,在编译阶段就会自动的有优化为StringBuilder.append()方法)然后在堆内存中会创建”He”对象,和内容为”Hello”的
对象,然后把这个对象的引用赋给s2;String s3 = s2.intern();去字符串常量池中找有没有值为”Hello”的对象,因为此时字符串常量池中是有的,所以会把字符串常量池中“Hello”对象的引用赋给s3,所以此时的s1的值和s3的值是相等的;s1和s2的值是不相等的

2:new String(引用)的情形

public void test3(){ 
   
        String m = "hello,world";
        String n = "hello,world";
        String u = new String(m);
        String v = new String("hello,world");
        System.out.println(m == u);
        System.out.println(u == v);
    }

分析String m = "hello,world" String n = "hello,world";这两句代码和前面的分析一样,会在字符串常量池中创建一个对象“hello,world”,m和n指向的是字符串常量池中的同一个对象;String u = new String(m);会在堆中生成一个新的对象,但是内部的字符数组引用这m内部的字符数组;String v = new String("hello,world");同样会生成一个新的字符数组对象在堆里面,此时因为在常量池中已经存在了 “hello,world”对象,v指向的是堆里面的对象

3:“Java”字符串

public void test7(){ 
   
        //发现原来是在JVM启动的时候调用了一些方法,在常量池中已经生成了"java"字符串常量,
         String s2 = new String("ja") + new String("va");
         String s3 =  s2.intern();
         String s4 = "java";
         System.out.println(s2 == s3);
         System.out.println(s3 == s4);
    }
结果是:
fasle
true

分析在String s2 = new String("ja") + new String("va");代码中做的事情就是在字符串常量池中存储了”ja”和”va”对象,在堆里面存储的是”java”对象,String s3 = s2.intern();在字符串常量池中寻找有没有“java”对象,由于JVM的 特殊性在JVM启动的时候调用了一些方法,在常量池中已经生成了“java”字符串常量,所以s3直接返回字符串常量池中已经存在的“java”,s4也是同样的道理,所以结果就非常的明显了

参考的部分文章

深入解析String#intern(美团技术团队)

String:字符串常量池(SegmentFault)

JDK1.8版本java字符串常量池里存的是String对象还是引用?(CSDN)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/164767.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月28日 上午6:36
下一篇 2022年7月28日 上午6:36


相关推荐

  • malloc、calloc、realloc的区别

    malloc、calloc、realloc的区别1 C 语言跟内存分配方式从静态存储区域分配 nbsp nbsp nbsp nbsp nbsp nbsp 内存在程序编译的时候就已经分配好 这块内存在程序的整个运行期间都存在 例如全局变量 static 变量 在栈上创建 nbsp nbsp nbsp nbsp nbsp nbsp 在执行函数时 函数内局部变量的存储单元都可以在栈上创建 函数执行结束时这些存储单元自动被释放 栈内存分配运算内置于处理器的指令集中 效率很高 但是分配的内存容量有限 从堆上分配 亦称动态内存分配 nbsp nbsp nbsp nbsp nbsp nbsp 程序在运行的时候用 malloc 或 new 申请任意多少的内存 程序员自己负责在何时用 free 或 delete 释放内存 动态

    2026年3月17日
    2
  • hashmap基本原理_哈希表的实现原理

    hashmap基本原理_哈希表的实现原理HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。  首先HashMap里面实现一个静态内部类Entry,其重要的属性有key,value,next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Ent

    2025年8月12日
    4
  • List去重3种方式

    List去重3种方式一、背景1.在实战中list去重是非常频繁的,下面就讲讲它的三种用法。二、第一种(原始代码去重)1.测试类publicclassDemoTest{publicstaticvoidmain(String[]args){ArrayList<String>list=Lists.newArrayList(“02″,”01”,”…

    2022年5月26日
    40
  • 私人 AI 随身带!OpenClaw+cpolar 外网访问完整教程

    私人 AI 随身带!OpenClaw+cpolar 外网访问完整教程

    2026年3月14日
    2
  • redis hscan用法

    redis hscan用法nbsp nbsp nbsp nbsp nbsp 最近项目需要将 redis 中的活跃数据定期同步到数据库中 需要对 hash 的 field 做遍历 查询资料发现 keys 和 hgetall 都存在一定的缺点 最后发现 hscan 满足需求 但是在我的 redis2 8 版本下面 无论怎么测试都无支持 hscankeycurs 每次希望取 10 条数据都直接将 redis 对应 key 中全部一次取出 这样显然不符合业务要求 如果数据量大

    2026年3月19日
    2
  • linux基本操作_shell入门教程

    linux基本操作_shell入门教程linux环境虚拟机管理使用VMwareWorkstationlunix系统使用redhat7.4(镜像.ios) linux常用命令::Id用户名查看用户信息查询端口号占用netstat-tunlp|grep端口号pwd查看当前文件夹目录cat查看文件,不可修改cat文件|more以分页模式查看文件,(空格键下一页,回车下移一行,q退…

    2022年10月3日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号