Java - String 的字面量、常量池、构造函数和intern()函数
一、内存中的 String 对象
Java 的堆和栈
- 对于基本数据类型变量和对象的引用,也就是局部变量表属于栈内存;
- 而通过 new 关键字和 constructor 创建的对象存放在堆内存;
直接的 "hello" 被称为字面量形式,在JDK1.7之后存放在位于堆内存的独立的常量池中;
```java
// 比如说:
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 对象,所以当使用"=="将
str2
与1
比较时,结果一定是false,因为两者的地址是不同的。
intern()函数和新的问题
intern()函数
先介绍一个神奇的函数—— intern(),它是一个native方法,不妨来看一下这个函数的介绍
- 返回值是一个标准的字符串形式;
- 返回值是与此对象具有相同内容的字符串,但保证来自字符串池;
- 对于两个字符串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");
对于结果,相信str1的两个输出结果都是可以理解的,str1创建后产生两个对象,保存在堆的 str1 和常量池中的 "1" 地址显然不同,而intern() 则是返回的"1"的地址,所以输出均为false;
而str2、str3、str4就变得诡异起来了,经过了字符串拼装之后,str2
和str2.intern()
神奇的具有了相同的地址,但同时,因为一个str4,str3
和str3.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() 总是没错的。
Java - String 的字面量、常量池、构造函数和intern()函数的更多相关文章
- String 的字面量、常量池、构造函数和intern()函数
一.内存中的 String 对象 Java 的堆和栈 对于基本数据类型变量和对象的引用,也就是局部变量表属于栈内存: 而通过 new 关键字和 constructor 创建的对象存放在堆内存: 直接的 ...
- Java堆、栈和常量池以及相关String的详细讲解(经典中的经典) (转)
原文链接 : http://www.cnblogs.com/xiohao/p/4296088.html 一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register). 这是最快的 ...
- Java堆、栈和常量池以及相关String的详细讲解
一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register). 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据 ...
- Java堆、栈和常量池以及相关String详解
一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register). 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据 ...
- Java堆、栈和常量池以及相关String的详细讲解(转)
一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register). 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据 ...
- JAVA字节码文件之常量池
一.常量池的内容 一个java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是class文件的资源仓库,比如java类中定义的方法与变量信息.常量池中主要存储两类常量:字面量(文本字符 ...
- [Effective JavaScript 笔记]第52条:数组字面量优于数组构造函数
js的优雅很大程序要归功于程序中常见的构造块(Object,Function及Array)的简明的字面量语法.字面量是一种表示数组的优雅方法. var a=[1,2,3,5,7,8]; 也可以使用构造 ...
- JAVA String对象和字符串常量的关系解析
JAVA String对象和字符串常量的关系解析 1 字符串内部列表 JAVA中所有的对象都存放在堆里面,包括String对象.字符串常量保存在JAVA的.class文件的常量池中,在编译期就确定好了 ...
- Java 中 String 的字面量与 intern 方法
下方代码主要说明: String b = new String("xyz") 创建2个对象,一个在常量池中的 "xyz",一个 String 实例对象,返回的 ...
随机推荐
- 关于Python pandas模块输出每行中间省略号问题
关于Python数据分析中pandas模块在输出的时候,每行的中间会有省略号出现,和行与行中间的省略号....问题,其他的站点(百度)中的大部分都是瞎写,根本就是复制黏贴以前的版本,你要想知道其他问题 ...
- Linux中FTP远程传输,SSH远程连接,以及SCP远程拷贝
常用服务器ftp.ssh 1. Linux常用服务器构建-ftp服务器 ftp服务器 FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”. 用于 ...
- 【Spark篇】---SparkSQL中自定义UDF和UDAF,开窗函数的应用
一.前述 SparkSQL中的UDF相当于是1进1出,UDAF相当于是多进一出,类似于聚合函数. 开窗函数一般分组取topn时常用. 二.UDF和UDAF函数 1.UDF函数 java代码: Spar ...
- 进程,线程,协程,异步IO知识点
进程: qq 要以一个整体的形式暴露给操作系统管理,里面包含对各种资源的调用,内存的管理,网络接口的调用等...对各种资源管理的集合 就可以成为 进程 线程: 是操作系统最小的调度单位, 是一串指令 ...
- ASP.NET Core WebAPI控制器返回类型的最佳选项
前言 从.NET Core 2.1版开始,到目前为止,控制器操作可以返回三种类型的WebApi响应.这三种类型都有自己的优点和缺点,但都缺乏满足REST和高可测性的选项. ASP.NET Core中可 ...
- 一个简单的案例带你入门Dubbo分布式框架
相信有很多小伙伴都知道,dubbo是一个分布式.高性能.透明化的RPC服务框架,提供服务自动注册.自动发现等高效服务治理方案,dubbo的中文文档也是非常全的,中文文档可以参考这里dubbo.io.由 ...
- Node.js与Sails~redis组件的使用
有段时间没写关于NodeJs的文章了,今天也是为了解决高并发的问题,而想起了这个东西,IIS的站点在并发量达到200时有了一个瓶颈,于是想到了这个对高并发支持比较好的框架,nodeJs在我之前写出一些 ...
- Paramiko模块简单使用
介绍 Paramiko 一个第三方包,需要单独安装我们知道远程批量主机管理,比如ansible.Fabric,不需要安装客户端的远程执行命令等,这些都是基于Python原生的SSH,相当于模拟了一个S ...
- linux centos6.5安装KVM
1.安装kvm,vnc软件 http://www.server110.com/kvm/201404/10467.html yum install kvm,缺啥补啥 yum install kvm km ...
- AppBoxFuture(六): 前端组件化开发
前面几篇都是在介绍结构化与非结构化的数据存储,本篇换换口味介绍一下框架是如何实现前端组件化开发的.首先得感谢Vue.ElementUI等优秀的前端开源项目,这些项目帮助作者快速实现了框架的两个前端 ...