String 的字面量、常量池、构造函数和intern()函数

时间:2022-08-27 09:15:56

一、内存中的 String 对象

Java 的堆和栈

  • 对于基本数据类型变量和对象的引用,也就是局部变量表属于栈内存
  • 而通过 new 关键字和 constructor 创建的对象存放在堆内存
  • 直接的 "hello" 被称为字面量形式,在JDK1.7之后存放在位于堆内存的独立的常量池中;
    // 比如说:
    String s1="hello";
    Scanner input = new Scanner();
    // 上面的语句中变量名 s1、input 存放在栈内存中,"hello" 为字面量,所以放在常量池,而用构造函数创建的对象放在堆内存中。

什么是String常量池

JVM为了减少字符串对象的重复创建,维护了一个特殊的内存,这段内存被称为字符串常量池或者字符串字面量池。我们所知道的几个String的特点都来源于此。

  • 在这个常量池中,共享所有的String对象 ,因此String对象不可被修改,因为一旦被修改那么同时引用此String对象的变量都会随之改变,所以被设计成不可修改的;
  • 也因此我们常常会听说String拼接字符串的性能较差;
  • 使用双引号声明的String对象会直接存储在常量池中,若已存在,则直接引用已存在的String对象;
  • 每个String对象都是唯一的,这样才能达到节约内存的目的;

补充说明 “==” 和 “equals()”

  • 在基本数据类型中,只可以使用“ == ”,也就是比较他们的值是否相同;
  • 而对于对象(包括 String )来说,“ == ”表示比较地址是否相同,“ equals() ”才表示比较他们内容是否相同;
  • equals()是object都拥有的一个函数,本身就要求对内部值进行比较;

二、String 的字面量和构造函数

1. 两者的不同

除了"1"这种字面量,还有一种就是使用构造函数 new String() 进行String对象的创建。

而对于String str1 = "1";String str2 = new String("1");两个语句在执行时,内存中的操作是不同的。

对于String str1 = "1";来说,和之前介绍的常量池一致,当语句执行时,JVM会首先检查常量池中是否存在该字面量:

  • 若存在,则直接返回此字面量的引用;
  • 若不存在,则在常量池中创建该字面量,返回其引用;

对于String str2 = new String("1");来说,当语句执行时,JVM同样会先检查常量池中是否存在对应的字面量:

  • 若存在,则在堆中创建String对象,在对象内部引用该字面量;
  • 若不存在,则先在常量池中创建字面量,然后在堆中创建String对象,在对象内部引用该字面量;

2. 初步结论

  • 无论任何时候,new String() 都会自己另行在堆中开辟空间,创建新的String对象;
  • 而假如常量池中不存在对应的字面量,new String() 会创建两个对象,一个放进常量池中,一个放进堆中;
  • 因为new String() 总是创建新的String 对象,所以当使用"=="将str21比较时,结果一定是false,因为两者的地址是不同的。

intern()函数和新的问题

intern()函数

先介绍一个神奇的函数—— intern(),它是一个native方法,不妨来看一下这个函数的介绍

  1. 返回值是一个标准的字符串形式;
  • 返回值是与此对象具有相同内容的字符串,但保证来自字符串池;
  • 对于两个字符串s、t,当且仅当s.equals(t)为true时,才能说s.intern()==t.intern()为true;
  • 当此方法被调用时,如果常量池中已经包含了一个和该对象内容相同的字符串,那就返回这个字符串;若不包含,如果大家有查看其它资料,他们都会说不存在则新建,但事实上,在接下来的问题之前,根本没有不存在的情况,字面量总是存在的;

新的问题

这个函数有什么用,个人认为,可以粗率地认为这个函数可以找到所有的String object在常量池中对应的字面量(存在则返回引用,不存在则创建后返回引用)。但是不难想到,之前的初步结论已经得出new String("")会确保两个对象的存在,那么intern()函数的存在有什么意义呢?为了得到一个String对象中引用的的源对象?

这时引入下面一段代码:

String str1 = new String("1");
System.out.println(str1 == str1.intern());
System.out.println(str1 == "1"); String str2 = new String("2") + new String("3");
System.out.println(str2 == str2.intern());
System.out.println(str2 == "23"); String str4="45";
String str3 = new String("4") + new String("5");
System.out.println(str3==str3.intern());
System.out.println(str3 == "45");

运行结果:

String 的字面量、常量池、构造函数和intern()函数

对于结果,相信str1的两个输出结果都是可以理解的,str1创建后产生两个对象,保存在堆的 str1 和常量池中的 "1" 地址显然不同,而intern() 则是返回的"1"的地址,所以输出均为false;

而str2、str3、str4就变得诡异起来了,经过了字符串拼装之后,str2str2.intern()神奇的具有了相同的地址,但同时,因为一个str4,str3str3.intern()相同的地址又变的不同起来;

所以新的问题就来源于字符串拼接,根据前文已经知道字符串是不可修改的,那么想要进行一次 String str2 = new String("2") + new String("3");这样的字符串拼接消耗就非常大了(相信大家都听过字符串拼接效率差的说法),所以JVM对其进行了优化,具体是如何优化的呢?

分析 - intern()结论

  • 如果是String str2 = "2" + "3";,则直接将"2"和"3"折叠为"23",然后直接作为字面量放入常量池中,也就是和String str2 = "23";没区别,具体可见String a="a"+"b"+"c"在内存中创建几个对象? - 陈肖恩的回答 - 知乎
  • 如果是 String str2 = new String("2") + new String("3");这种情况,JVM同样会进行优化,目前根据我的调查,会被优化成三个对象的创建——在常量池中创建"2"、"3",在堆中创建内容为"23"的String对象,大家可能会奇怪,不在常量池创建"23"吗?目前看是不会的;
  • 之前我也说到intern() 根本没有不存在的情况,但眼下这种情况是真的不存在了,intern()采取了一种截然不同的处理方案——不是在常量池中建立字面量,而是直接将该对象自身的引用复制到常量池中,所以代码的第二段就不难解释了,此时堆中的str2才是真正的源字符串,而常量池也只是对它的引用。
  • 而使用intern() 场和也变得显而易见,当你需要进行大量可能会重复的字符串的拼接的时候,为了避免内存的浪费进而导致GC清理无用字符串降低性能,那就可以使用intern()了。

三、其他 String 类相关结论

构造函数结论

不难看出,总是new String("")这样的函数在浪费内存,降低性能,所以大家在写程序的时候应该尽量直接使用字面量,而避免构造函数的使用。

String 是否为空的结论

String 存在一个方法叫 str.isEmpty(),如果查看源代码就会发现和 str.length()==0 没有任何区别。

public boolean isEmpty() { return value.length == 0;} //源代码

// 何时出现此种情况:
String s1 = new String();
String s1 = new String("");
String s1 = "";

String 是否为null的结论

null即未指定对象,如果直接使用会出现所谓的空指针错误。值得注意的是第二种情况,字符串数组在创建之后并不会像字符串新建时一样初始化为长度为1的字符串,而是空指针。

// 何时出现此种情况:
String s1 = null;
String[] s1 = new String[n];

String equals() 的结论

所以根据 equals() 的定义就能发现,此无论任何情况下, equals() 总是比较两个字符串的内容,无论是否开辟内存或别的怎样,假如需求就是简单地进行字符串匹配,使用 equals() 总是没错的。

String 的字面量、常量池、构造函数和intern()函数的更多相关文章

  1. Java - String 的字面量、常量池、构造函数和intern()函数

    一.内存中的 String 对象 Java 的堆和栈 对于基本数据类型变量和对象的引用,也就是局部变量表属于栈内存: 而通过 new 关键字和 constructor 创建的对象存放在堆内存: 直接的 ...

  2. [Effective JavaScript 笔记]第52条:数组字面量优于数组构造函数

    js的优雅很大程序要归功于程序中常见的构造块(Object,Function及Array)的简明的字面量语法.字面量是一种表示数组的优雅方法. var a=[1,2,3,5,7,8]; 也可以使用构造 ...

  3. Java 中 String 的字面量与 intern 方法

    下方代码主要说明: String b = new String("xyz")  创建2个对象,一个在常量池中的 "xyz",一个 String 实例对象,返回的 ...

  4. java基础进阶一:String源码和String常量池

    作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/8046564.html 邮箱:moyi@moyib ...

  5. 从字符串到常量池,一文看懂String类设计

    从一道面试题开始 看到这个标题,你肯定以为我又要讲这道面试题了 // 这行代码创建了几个对象? String s3 = new String("1"); 是的,没错,我确实要从这里 ...

  6. 从Java的字符串池、常量池理解String的intern()

    前言 逛知乎遇到一个刚学Java就会接触的字符串比较问题: 通常,根据"==比较的是地址,equals比较的是值"介个定理就能得到结果.但是String有些特殊,通过new Str ...

  7. String放入运行时常量池的时机与String.intern()方法解惑

    运行时常量池概述 Java运行时常量池中主要存放两大类常量:字面量和符号引用.字面量比较接近于Java语言层面的常量概念,如文本字符串.声明为final的常量值等. 而符号引用则属于编译原理方面的概念 ...

  8. Java String 常量池理解

    String:字符串常量池 作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串常量池: 字 ...

  9. String:字符串常量池

    String:字符串常量池 作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串常量池: 字 ...

随机推荐

  1. 谷歌发布的首款基于HTTP/2和protobuf的RPC框架:GRPC

    Google 刚刚开源了grpc,  一个基于HTTP2 和 Protobuf 的高性能.开源.通用的RPC框架.Protobuf 本身虽然提供了RPC  的定义语法,但是一直以来,Google 只开 ...

  2. 使用crypto模块实现md5加密功能(解决中文加密前后端不一致的问题)

    正常情况下使用md5加密 var crypto = require('crypto'); var md5Sign = function (data) { var md5 = crypto.create ...

  3. RSA加密解密(python版)

    RSA的算法涉及三个参数,n.e.d. 其中,n是两个大质数p.q的积,n的二进制表示时所占用的位数,就是所谓的密钥长度. e1和d是一对相关的值,e可以任意取,但要求e与(p-1)*(q-1)互质: ...

  4. Ubuntu下搭建java开发环境

    JDK安装: 1. 在http://www.oracle.com/technetwork/java/javase/downloads/index.html上下载相应版本的JDK环境,这里我使用的是jd ...

  5. strcpy_s与strcpy对照

    strcpy_s和strcpy()函数功能几乎相同.strcpy函数.就象gets函数一样,它没有方法来保证有效的缓冲区尺寸,所以它仅仅能假定缓冲足够大来容纳要拷贝的字符串.在程序执行时,这将导致不可 ...

  6. HDU1532 Drainage Ditches 【最大流量】

    Drainage Ditches Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  7. linux里的那么点东西(持续更新)

    作为一个程序猿的确是应该上的了windows,下的了linux的.但是由于没有对linux产生一些刚性的需求,所以使用的次数还是略少,对一些基本的concept和command还是有一些生疏.借着最近 ...

  8. 求出100以内的素数(java实现)

    j package test1; //2018/11/30 //求100以内的所有素数 public class Main10 { public static void main(String[] a ...

  9. VUE-Windows系统下搭建vue环境

    一.安装node.js(https://nodejs.org/en/) 下载完毕后,可以安装node,建议不要安装在系统盘(如C:).注意记下路径..   此处默认安装这4项即可,点击Next按钮. ...

  10. [Spring] Spirng中的AOP进行事务的传播属性和事务隔离级别

    通知注解 前置通知(@Before):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常) 返回后通知(@AfterReturning):在某连接点 ...