【Java进阶篇】String中 intern 的原理是什么?

在这里插入图片描述

String中 intern 的原理

  • ✔️ 典型解析
    • ✔️小思考(回顾)
  • ✔️字面量
  • ✔️intern
  • ✔️ intern原理
    • ✔️a和1有什么不同
    • ✔️答案


✔️ 典型解析


字符串常量池中的常量有两种来源:


1、 字面量会在编译期先进入到Class常量池,然后再在运行期进去到字符串池


2、在运行期通过intern将字符串对象手动添加到字符串常量池中


✔️小思考(回顾)


💡String a = “ab”; String b = “a” + “b”; a == b 吗?


在Java中,对于字符串使用 == 比较的是字符串对象的引用地址是否相同。


因为a和b都是由字面量组成的字符串,它们的引用地址在编译时就已经确定了,并且在编译之后,会把字面量直接合在一起。因此,a == b的结果为true,因为它们指向的是同一个字符串对象。


本站跳转博主博文:String 、StringBuffer、StringBuilder 三者区别


✔️字面量


在计算机科学中,字面量 (literal) 是用于表达源代码中一个固定值的表示法 (notation) 。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如: 整数、浮点数以及字符串,而有很多也对布尔类型和字符类型的值也支持字面量表示,还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。


以上是关于计算机科学中关于字面量的解释,并不是很容易理解。说简单点,字面量就是指由字母、数字等构成的字符串或者数值。


字面量只可以右值出现,所谓右值是指等号右边的值,如: int a=123这里的 a 为左值,123为右值在这个例子中 123 就是字面量。


int a = 123;
String s = "xinbaobaba";

上面的代码事例中,123 和 xinbaobaba 都是字面量。


JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串常量池。


在JVM运行时区域的方法区中,有一块区域是运行时常量池,主要用来存储编译期生成的各种字面量和符号引用。


了解 Class文件结构或者做过 Java 代码的反编译的朋友可能都知道,在java代码被iavac编译之后,文件结构中是包含一部分Constant pool 的。比如以下代码:


public static void main(String[] args) {String s ="xinbaobaba";
}

经过编译后,常量池内容如下:


Classfile /F:/Java/WhileAndFor.classLast modified 202412; size 290 bytesSHA-256 checksum f92f9977740415349cbf9e3ac5d853f97989c05ac8ebc768a9f0929d3382b39bCompiled from "WhileAndFor.java"
public class WhileAndForminor version: 0major version: 61flags: (0x0021) ACC_PUBLIC, ACC_SUPERthis_class: #9                          // WhileAndForsuper_class: #2                         // java/lang/Objectinterfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:#1 = Methodref          #2.#3          // java/lang/Object."<init>":()V#2 = Class              #4             // java/lang/Object#3 = NameAndType        #5:#6          // "<init>":()V#4 = Utf8               java/lang/Object#5 = Utf8               <init>#6 = Utf8               ()V#7 = String             #8             // xinbaobaba#8 = Utf8               xinbaobaba#9 = Class              #10            // WhileAndFor#10 = Utf8               WhileAndFor#11 = Utf8               Code#12 = Utf8               LineNumberTable#13 = Utf8               main#14 = Utf8               ([Ljava/lang/String;)V#15 = Utf8               SourceFile#16 = Utf8               WhileAndFor.java
{public WhileAndFor();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 5: 0public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=1, locals=2, args_size=10: ldc           #7                  // String xinbaobaba2: astore_13: returnLineNumberTable:line 8: 0line 9: 3
}
SourceFile: "WhileAndFor.java"

上面的class文件中的常量池中,比较重要的几个内容:


 #6 = Utf8               ()V#8 = Utf8               xinbaobaba#10 = Utf8               WhileAndFor

上面的几个常量中, v就是前面提到的符号引用,而 xinbaobaba 就是前面提到的字面量


而Class文件中的常量池部分的内容,会在运行期被运行时常量池加载进去。


✔️intern


intern 的作用是这样的:


如果字符串池中已经存在一个等于该字符串的对象,intern() 方法会返回这个已存在的对象的引用


如果字符串池中没有等于该字符串的对象,intern() 方法会将该字符串添加到字符串池中,并返回对新添加的字符串对象的引用。


String s = new String("xinbao") + new String("baba");
s .intern();

所以,无论何时通过 intern()方法获取字符串的引用,都会得到字符串池中的引用,这样可以确保相同的字符串在内存中只有一个实例。


很多人以为知道以上信息,就算是了解 intern 了,那么请回答一下这个问题:


public static void main(String[] args) {String s1 = new String("x");s1.intern();String s2 = "x" ;System.out.println(s1 == s2); // falseString s3 = new String("a") + new String("a");s3.intern();String s4 = "aa";System.out.println(s3 == s4);// true
}

大家可以在JDK 1.7以上版本中尝试运行以上两段代码,就会发现,s1 == s2的结果是 false,但是s3 == s4的结果是 true。


这是为什么呢? ( 后文所有case均基于JDK 1.8运行 )


✔️ intern原理


了解其原理,我们继续分析上面的代码:


public static void main(String[] args) {String s1 = new String("x"); // ①s1.intern();//②String s2 = "x" ;//③System.out.println(s1 == s2); // ④           falseString s3 = new String("a") + new String("a"); //⑤s3.intern(); //⑥String s4 = "aa"; //⑦System.out.println(s3 == s4);// ⑧           true
}

这个类被编译后,Class常量池中应该有 “a” 和 “aa” 这两个字符串,这两个字符串最终会进到字符串池。但是,字面量 “a” 在代码①这一行,就会被存入字符串池,而字面量"aa"则是在代码⑦这一行会存入字符串池。


以上代码的执行过程:


第①行,,new 一个 String 对象,并让 s1指向他

第②行,,对 s1执行 intern,但是因为"a"这个字符串已经在字符串池中,所以会直接返回原来的引用,但是并没有赋值给任何一个变量。

第③行,s2指向常量池中的 "a” 。

所以,s1 和 s2并不相等!

第⑤行,new 一个 String 对象,并让 s3 指向他

第⑥行,对s3 执行 intern,但是目前字符串池中还没有"aa"这个字符串,于是会把 <s3指向的String对象的引用> 放入 <字符串常量池>

第⑦行,因为"aa"这个字符串已经在字符串池中,所以会直接返回原来的引用,并赋值给 s4

所以,s3和 s4 相等!


而,如果我们对代码稍微做一下修改:


String s = "aa"; //①
String s3 = new String("a") + new String("a");// ②
s3.intern();// ③
String s4 ="aa";
System.out.printIn(s3 == s4);// ④

以上代码得到的结果是 : false


第①行,创建一个字符串aa,并且因为它是字面量,所以把他放到字符串池

第②行,new一个 String 对象,并让 s3 指向他

第③行,对 s3 执行 intern,但是目前字符串池中已经有 “aa” 这个字符串,所以会直接返回s的引用但是并没有对 s3 进行赋值

第④行,因为 ”aa" 这个字符串已经在字符串池中,所以会直接返回原来的引用,即 s 的引用,并赋值给 s4; 所以,s3和 s4不相等。


✔️a和1有什么不同


关于这个问题,我们还有一个变型,可以帮大家更好的理解intern,请大家分别在JDK 1.8和JDK 11及以上的版本中执行以下代码:


String s3 = new String("1") + new String("1");// ①
s3.intern(); // ②
String s4 = "11";
System.out.println(s3 == s4); //③

你会发现,在JDK 1.8中,以上代码得到的结果是true,而JDK 11及以上的版本中结果却是false。


那么,再稍作修改呢? 在目前的所有JDK版本中,执行以下代码 :


String s3 = new String("3") + new String("3");//①
s3.intern();//②
String s4 = "33";
System.out.println(s3 == s4);// ③

得到的结果也是true,知道什么原因吗?

✔️答案

出现上述现象,肯定是因为在JDK 11 及以上的版本中,"11"这个字面量已经被提前存入字符串池了。那什么时候存进去的呢? (这个问题,全网应该没人提过)


经过我的攻克,终于发现端倪,就在下面代码中:


/** Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.*********************/package com.sun.tools.javac.code;import java.util.*;import javax.lang.model.SourceVersion;
import static javax.lang.model.SourceVersion.*;import com.sun.tools.javac.jvm.Target;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.JCDiagnostic.Error;
import com.sun.tools.javac.util.JCDiagnostic.Fragment;import static com.sun.tools.javac.main.Option.*;/** The source language version accepted.**  <p><b>This is NOT part of any supported API.*  If you write code that depends on this, you do so at your own risk.*  This code and its internal interfaces are subject to change or*  deletion without notice.</b>*/
public enum Source {/** 1.0 had no inner classes, and so could not pass the JCK. */// public static final Source JDK1_0 =              new Source("1.0");/** 1.1 did not have strictfp, and so could not pass the JCK. */// public static final Source JDK1_1 =              new Source("1.1");/** 1.2 introduced strictfp. */JDK1_2("1.2"),/** 1.3 is the same language as 1.2. */JDK1_3("1.3"),/** 1.4 introduced assert. */JDK1_4("1.4"),/** 1.5 introduced generics, attributes, foreach, boxing, static import,*  covariant return, enums, varargs, et al. */JDK5("5"),/** 1.6 reports encoding problems as errors instead of warnings. */JDK6("6"),/** 1.7 introduced try-with-resources, multi-catch, string switch, etc. */JDK7("7"),/** 1.8 lambda expressions and default methods. */JDK8("8"),/** 1.9 modularity. */JDK9("9"),/** 1.10 local-variable type inference (var). */JDK10("10"),/** 1.11 covers the to be determined language features that will be added in JDK 11. */JDK11("11");private static final Context.Key<Source> sourceKey = new Context.Key<>();public static Source instance(Context context) {Source instance = context.get(sourceKey);if (instance == null) {Options options = Options.instance(context);String sourceString = options.get(SOURCE);if (sourceString != null) instance = lookup(sourceString);if (instance == null) instance = DEFAULT;context.put(sourceKey, instance);}return instance;}public final String name;private static final Map<String,Source> tab = new HashMap<>();static {for (Source s : values()) {tab.put(s.name, s);}tab.put("1.5", JDK5); // Make 5 an alias for 1.5tab.put("1.6", JDK6); // Make 6 an alias for 1.6tab.put("1.7", JDK7); // Make 7 an alias for 1.7tab.put("1.8", JDK8); // Make 8 an alias for 1.8tab.put("1.9", JDK9); // Make 9 an alias for 1.9tab.put("1.10", JDK10); // Make 10 an alias for 1.10// Decline to make 1.11 an alias for 11.}private Source(String name) {this.name = name;}public static final Source MIN = Source.JDK6;private static final Source MAX = values()[values().length - 1];public static final Source DEFAULT = MAX;public static Source lookup(String name) {return tab.get(name);}public Target requiredTarget() {if (this.compareTo(JDK11) >= 0) return Target.JDK1_11;if (this.compareTo(JDK10) >= 0) return Target.JDK1_10;if (this.compareTo(JDK9) >= 0) return Target.JDK1_9;if (this.compareTo(JDK8) >= 0) return Target.JDK1_8;if (this.compareTo(JDK7) >= 0) return Target.JDK1_7;if (this.compareTo(JDK6) >= 0) return Target.JDK1_6;if (this.compareTo(JDK5) >= 0) return Target.JDK1_5;if (this.compareTo(JDK1_4) >= 0) return Target.JDK1_4;return Target.JDK1_1;}/*** Models a feature of the Java programming language. Each feature can be associated with a* minimum source level, a maximum source level and a diagnostic fragment describing the feature,* which is used to generate error messages of the kind {@code feature XYZ not supported in source N}.*/public enum Feature {DIAMOND(JDK7, Fragments.FeatureDiamond, DiagKind.NORMAL),MULTICATCH(JDK7, Fragments.FeatureMulticatch, DiagKind.PLURAL),IMPROVED_RETHROW_ANALYSIS(JDK7),IMPROVED_CATCH_ANALYSIS(JDK7),MODULES(JDK9, Fragments.FeatureModules, DiagKind.PLURAL),TRY_WITH_RESOURCES(JDK7, Fragments.FeatureTryWithResources, DiagKind.NORMAL),EFFECTIVELY_FINAL_VARIABLES_IN_TRY_WITH_RESOURCES(JDK9, Fragments.FeatureVarInTryWithResources, DiagKind.PLURAL),BINARY_LITERALS(JDK7, Fragments.FeatureBinaryLit, DiagKind.PLURAL),UNDERSCORES_IN_LITERALS(JDK7, Fragments.FeatureUnderscoreLit, DiagKind.PLURAL),STRINGS_IN_SWITCH(JDK7, Fragments.FeatureStringSwitch, DiagKind.PLURAL),DEPRECATION_ON_IMPORT(MIN, JDK8),SIMPLIFIED_VARARGS(JDK7),OBJECT_TO_PRIMITIVE_CAST(JDK7),ENFORCE_THIS_DOT_INIT(JDK7),POLY(JDK8),LAMBDA(JDK8, Fragments.FeatureLambda, DiagKind.PLURAL),METHOD_REFERENCES(JDK8, Fragments.FeatureMethodReferences, DiagKind.PLURAL),DEFAULT_METHODS(JDK8, Fragments.FeatureDefaultMethods, DiagKind.PLURAL),STATIC_INTERFACE_METHODS(JDK8, Fragments.FeatureStaticIntfMethods, DiagKind.PLURAL),STATIC_INTERFACE_METHODS_INVOKE(JDK8, Fragments.FeatureStaticIntfMethodInvoke, DiagKind.PLURAL),STRICT_METHOD_CLASH_CHECK(JDK8),EFFECTIVELY_FINAL_IN_INNER_CLASSES(JDK8),TYPE_ANNOTATIONS(JDK8, Fragments.FeatureTypeAnnotations, DiagKind.PLURAL),ANNOTATIONS_AFTER_TYPE_PARAMS(JDK8, Fragments.FeatureAnnotationsAfterTypeParams, DiagKind.PLURAL),REPEATED_ANNOTATIONS(JDK8, Fragments.FeatureRepeatableAnnotations, DiagKind.PLURAL),INTERSECTION_TYPES_IN_CAST(JDK8, Fragments.FeatureIntersectionTypesInCast, DiagKind.PLURAL),GRAPH_INFERENCE(JDK8),FUNCTIONAL_INTERFACE_MOST_SPECIFIC(JDK8),POST_APPLICABILITY_VARARGS_ACCESS_CHECK(JDK8),MAP_CAPTURES_TO_BOUNDS(MIN, JDK7),PRIVATE_SAFE_VARARGS(JDK9),DIAMOND_WITH_ANONYMOUS_CLASS_CREATION(JDK9, Fragments.FeatureDiamondAndAnonClass, DiagKind.NORMAL),UNDERSCORE_IDENTIFIER(MIN, JDK8),PRIVATE_INTERFACE_METHODS(JDK9, Fragments.FeaturePrivateIntfMethods, DiagKind.PLURAL),LOCAL_VARIABLE_TYPE_INFERENCE(JDK10),IMPORT_ON_DEMAND_OBSERVABLE_PACKAGES(JDK1_2, JDK8);enum DiagKind {NORMAL,PLURAL;}private final Source minLevel;private final Source maxLevel;private final Fragment optFragment;private final DiagKind optKind;Feature(Source minLevel) {this(minLevel, null, null);}Feature(Source minLevel, Fragment optFragment, DiagKind optKind) {this(minLevel, MAX, optFragment, optKind);}Feature(Source minLevel, Source maxLevel) {this(minLevel, maxLevel, null, null);}Feature(Source minLevel, Source maxLevel, Fragment optFragment, DiagKind optKind) {this.minLevel = minLevel;this.maxLevel = maxLevel;this.optFragment = optFragment;this.optKind = optKind;}public boolean allowedInSource(Source source) {return source.compareTo(minLevel) >= 0 &&source.compareTo(maxLevel) <= 0;}public boolean isPlural() {Assert.checkNonNull(optKind);return optKind == DiagKind.PLURAL;}public Fragment nameFragment() {Assert.checkNonNull(optFragment);return optFragment;}public Fragment fragment(String sourceName) {Assert.checkNonNull(optFragment);return optKind == DiagKind.NORMAL ?Fragments.FeatureNotSupportedInSource(optFragment, sourceName, minLevel.name) :Fragments.FeatureNotSupportedInSourcePlural(optFragment, sourceName, minLevel.name);}public Error error(String sourceName) {Assert.checkNonNull(optFragment);return optKind == DiagKind.NORMAL ?Errors.FeatureNotSupportedInSource(optFragment, sourceName, minLevel.name) :Errors.FeatureNotSupportedInSourcePlural(optFragment, sourceName, minLevel.name);}}public static SourceVersion toSourceVersion(Source source) {switch(source) {case JDK1_2:return RELEASE_2;case JDK1_3:return RELEASE_3;case JDK1_4:return RELEASE_4;case JDK5:return RELEASE_5;case JDK6:return RELEASE_6;case JDK7:return RELEASE_7;case JDK8:return RELEASE_8;case JDK9:return RELEASE_9;case JDK10:return RELEASE_10;case JDK11:return RELEASE_11;default:return null;}}
}

xdm,在JDK 11 的源码中,定义了”11"这个字面量,那么他会提前进入到字符串池中,那么后续的 intern 的过程就会直接从字符串池中获取到这个字符串引用。


按照这个思路,大家可以在JDK 11中执行以下代码:


String s3 = new String("1") + new String("1");
s3.intern();
String s4 ="11";
System.out.println(s3 == s4);String s3 = new String("1") + new String("2");
s3 .intern();
String s4 ="12";
System.out.println(s3 == s4);

得到的结果就是false和true


或者我是在JDK 21中分别执行了以下代码:


String s3 = new String("2") + new String("1");
s3 .intern();
String s4 = "21";
System.out.printn(s3 == s4);String s3 = new String("2") + new String("2");
s3.intern();
String s4 = "22";
System.out.println(s3 == s4);

得到的结果就也是false和true

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/315353.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统编程--gcc编译

这里写目录标题 gcc编译四步骤简介注意点参数-I-D 二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 gcc编译四步骤 简介 以上是gcc编译的四步骤&#xff0c;每个步骤生成对应…

【java爬虫】使用element-plus进行个股详细数据分页展示

前言 前面的文章我们讲述了获取详细个股数据的方法&#xff0c;并且使用echarts对个股的价格走势图进行了展示&#xff0c;本文将编写一个页面&#xff0c;对个股详细数据进行展示。别问涉及到了element-plus中分页的写法&#xff0c;对于这部分知识将会做重点讲解。 首先看一…

强化学习:PPO

PPO简介 我们在之前的项目中介绍了基于价值的强化学习算法DQN,基于策略的强化学习算法REINFORCE,基于价值和策略的组合算法Actor-Critic. 对于基于策略分方法&#xff1a;参数化智能体的策略&#xff0c;并设计衡量策略好坏的目标函数&#xff0c;通过梯度上升的方法来最大化这…

python调用openai api报错self._sslobj.do_handshake()OSError: [Errno 0] Error

python调用openai api报错self._sslobj.do_handshake()OSError: [Errno 0] Error 废话不说&#xff0c;先上代码&#xff0c;根据官网的介绍写的,chatgpt3.5 api简单调用 import os from openai import OpenAI from dotenv import load_dotenv# 加载 .env 文件中的变量 load_…

三层架构概述

三层架构就是把整个软件的代码分为三个层次&#xff0c;分层的目的是&#xff1a;规范代码&#xff0c;大型软件需要团队配合的时候问题就来了&#xff0c;由于每个程序员风格不一样&#xff0c;而开发软件大量的代码风格不统一就会造成后期调试和维护出现问题&#xff0c;然而…

LeetCode刷题---有效的数独

解题思路&#xff1a; 该题通过哈希表(数组)计数来解决,因为矩阵是一个9*9的固定矩阵 定义二维数组rows,columns和三维度数组subboxes来对矩阵中第i行第j列数字在行、列和九宫格中出现的次数计数。 如果是一个有效的数独&#xff0c;那么矩阵中某个格子中的数字出现的次数在以上…

使用Redis进行搜索

文章目录 构建反向索引 构建反向索引 在Begin-End区域编写 tokenize(content) 函数&#xff0c;实现文本标记化的功能&#xff0c;具体参数与要求如下&#xff1a; 方法参数 content 为待标记化的文本&#xff1b; 文本标记的实现&#xff1a;使用正则表达式提取全小写化后的…

openssl 命令详解

openssl genrsa 命令产生私钥 openssl genrsa 命令是会用来生成 RSA 私有秘钥&#xff0c;不会生成公钥&#xff0c;因为公钥提取自私钥。生成时是可以指定私钥长度和密码保护。 如果需要查看公钥或生成公钥&#xff0c;可以使用 openssl rsa 命令。 命令语法&#xff1a; ope…

华为交换机入门(六):VLAN的配置

VLAN&#xff08;Virtual Local Area Network&#xff09;即虚拟局域网&#xff0c;是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。VLAN内的主机间可以直接通信&#xff0c;而VLAN间不能直接互通&#xff0c;从而将广播报文限制在一个VLAN内。 VLAN 主要用来解决如何…

案例分享:Qt多国语言输入法软键盘

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/135346374 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

一文带你了解,AGV和AMR有哪些区别和功能

AGV&#xff08;Automated Guided Vehicle&#xff09;和AMR&#xff08;Autonomous Mobile Robot&#xff09;是两种自动化移动设备&#xff0c;它们在某些方面相似&#xff0c;但也存在一些关键区别。 1、导航技术&#xff1a; AGV&#xff1a; AGV通常使用预先定义的路径或…

【C++】浅拷贝 / 深拷贝 / 写时拷贝

文章目录 1. 经典的string类问题2. 浅拷贝3. 深拷贝3.1 传统写法的String类3.2 现代写法的String类 4. 写时拷贝 1. 经典的string类问题 上一篇博客已经对string类进行了简单的介绍&#xff0c;大家只要能够正常使用即可。 链接&#xff1a;【C】string 在面试中&#xff0c;面…