一文带你了解如何正确使用Java中的字符串常量池
作者:JAVA旭阳 发布时间:2022-07-13 01:35:30
前言
研究表明,Java堆中对象占据最大比重的就是字符串对象,所以弄清楚字符串知识很重要,本文主要重点聊聊字符串常量池。Java中的字符串常量池是Java堆中的一块特殊存储区域,用于存储字符串。它的实现是为了提高字符串操作的性能并节省内存。它也被称为String Intern Pool
或String Constant Pool
。那让我来看看究竟是怎么一回事吧。
理解字符串常量池
当您从在类中写一个字符串字面量时,JVM将首先检查该字符串是否已存在于字符串常量池中,如果存在,JVM 将返回对现有字符串对象的引用,而不是创建新对象。我们通过一个例子更好的来理解。
比如下面的代码:
String s1 = "Harry Potter";
String s2 = "The Lord of the Rings";
String s3 = "Harry Potter";
在这段代码中,JVM 将创建一个值为“Harry Potter
”的字符串对象,并将其存储在字符串常量池中。s1和s3都将是对该单个字符串对象的引用。
如果s2的字符串内容“The Lord of the Rings
”不存在于池中,则在字符串池中生成一个新的字符串对象。
两种创建字符串方式
在 Java
编程语言中有两种创建 String
的方法。第一种方式是使用String Literal
字符串字面量的方式,另一种方式是使用new
关键字。他们创建的字符串对象是都在常量池中吗?
字符串字面量的方式创建
String s1 = "Harry Potter";
String s2 = "The Lord of the Rings";
String s3 = "Harry Potter";
new
关键字创建
String s4 = new String("Harry Potter");
String s5 = new String("The Lord of the Rings");
我们来比较下他们引用的是否是同一个对象:
s1==s3 //真
s1==s4 //假
s2==s5 //假
使用 == 运算符比较两个对象时,它会比较内存中的地址。
正如您在上面的图片和示例中看到的,每当我们使用new
运算符创建字符串时,它都会在 Java 堆中创建一个新的字符串对象,并且不会检查该对象是否在字符串常量池中。
那么我现在有个问题,如果是字符串拼接的情况,又是怎么样的呢?
字符串拼接方式
前面讲清楚了通过直接用字面量的方式,也就是引号的方式和用new关键字创建字符串,他们创建出的字符串对象在堆中存储在不同的地方,那么我们现在来看看用+
这个运算符拼接会怎么样。
例子1
public static void test1() {
// 都是常量,前端编译期会进行代码优化
// 通过idea直接看对应的反编译的class文件,会显示 String s1 = "abc"; 说明做了代码优化
String s1 = "a" + "b" + "c";
String s2 = "abc";
// true,有上述可知,s1和s2实际上指向字符串常量池中的同一个值
System.out.println(s1 == s2);
}
常量与常量的拼接结果在常量池,原理是编译期优化。
例子2
public static void test5() {
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4); // true 编译期优化
System.out.println(s3 == s5); // false s1是变量,不能编译期优化
System.out.println(s3 == s6); // false s2是变量,不能编译期优化
System.out.println(s3 == s7); // false s1、s2都是变量
System.out.println(s5 == s6); // false s5、s6 不同的对象实例
System.out.println(s5 == s7); // false s5、s7 不同的对象实例
System.out.println(s6 == s7); // false s6、s7 不同的对象实例
}
只要其中有一个是变量,结果就在堆中, 变量拼接的底层原理其实是StringBuilder
。
例子3:
public void test6(){
String s0 = "beijing";
String s1 = "bei";
String s2 = "jing";
String s3 = s1 + s2;
System.out.println(s0 == s3); // false s3指向对象实例,s0指向字符串常量池中的"beijing"
String s7 = "shanxi";
final String s4 = "shan";
final String s5 = "xi";
String s6 = s4 + s5;
System.out.println(s6 == s7); // true s4和s5是final修饰的,编译期就能确定s6的值了
}
不使用final修饰,即为变量。如s3行的s1和s2,会通过new StringBuilder进行拼接
使用final修饰,即为常量。会在编译器进行代码优化。
妙用String.intern() 方法
前面提到new关键字创建出来的字符串对象以及某些和变量进行拼接不会在字符串常量池中,而是直接在堆中新建了一个对象。这样不大好,做不到复用,节约不了空间。那有什么好办法呢?intern()
就派上用场了,这个非常有用。
intern()
方法的作用可以理解为主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。
String s6 = new String("The Lord of the Rings").intern();
s2==s6 //真
s2==s5 //假
字符串常量池有多大
关于字符串常量池究竟有多大,我也说不上来,但是讲清楚它底层的数据结构,也许你就明白了。
字符串常量池是一个固定大小的HashTable
,哈希表,默认值大小长度是1009
。如果放进String Pool
的String
非常多,就会造成Hash
冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern
时性能会大幅下降。
使用-XX:StringTablesize
可设置StringTable
的长度
在jdk6中
StringTable
是固定的,就是1009
的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTable Size
设置没有要求在jdk7中,StringTable的长度默认值是
60013
,StringTable Size
设置没有要求
在jdk8中,设置StringTable
长度的话,1009
是可以设置的最小值
字符串常量池的优缺点
字符串池的优点
提高性能。由于 JVM 可以返回对现有字符串对象的引用而不是创建新对象,因此使用字符串池时字符串操作更快。
共享字符串,节省内存。字符串池允许您在不同的变量和对象之间共享字符串,通过避免创建不必要的字符串对象来帮助节省内存。
字符串池的缺点
它有可能导致性能下降。从池中检索字符串需要搜索池中的所有字符串,这可能比简单地创建一个新的字符串对象要慢。如果程序创建和丢弃大量字符串,则尤其如此,因为每次使用字符串时都需要搜索字符串池。
来源:https://juejin.cn/post/7181322531863806007
猜你喜欢
- 一 应用规划: ※ 确定功能。 ※ 必须的界面及界面跳转的流程。
- 测试代码pom.xml:<?xml version="1.0" encoding="UTF-8"
- ActiveMQ是Apache的一个开源项目,它是一个功能强劲的开源消息总线,也是一个中间件产品,它是JMS的一个实现。在介绍ActiveM
- 本文实例为大家分享了Java实现分页功能的具体代码,供大家参考,具体内容如下不用根据改变SQL的形式去查询; 直接查询所有的数据,根据页码自
- git仓库直达List<String> strings = Lists.newArrayList("name=kk&q
- Java常用类库Math类Math包含用于执行基本数字运算的方法,例如基本指数,对数,平方根和三角函数一、Field SummaryModi
- 本文实例讲述了Java泛型定义与用法。分享给大家供大家参考,具体如下:1. 泛型的由来先看如下代码:import java.util.Lis
- 第一次接触到随机数还是在c语言里面 使用的是 rand(); 但是重新执行一次的时候会发现,诶,居然和上一次执行的结果是一样的,因为没有初始
- 1.Java进程的创建 Java提供了两种方法用来启动进程或其它程序: (1)使用Runtime的exec()方法 (2)使用Process
- 前言项目使用redis作为缓存数据,但面临着问题,比如,项目A,项目B都用到redis,而且用的redis都是一套集群,这样会带来一些问题。
- 查询文档 & 基本操作为了方便学习, 本节中所有示例沿用上节的索引按照ID单个GET class_1/_doc/1查询结果:{ &n
- 前言:小伙伴说能不能用springboot整合一下mybatis多数据源不使用JPA进行数据库连接操作。那么说干就干创建一个springbo
- 1.取整运算符取整从字面意思理解就是被除数到底包含几个除数,也就是能被整除多少次,那么它有哪些需要注意的地方呢?先看下面的两端代码: &nb
- 介绍备忘录模式(Memento Pattern)是一种行为型设计模式,它允许在不破坏封装性的前提下,捕获并保存一个对象的内部状态,并在之后可
- 轨迹压缩算法场景描述给定一个GPS数据记录文件,每条记录包含经度和维度两个坐标字段,根据距离阈值压缩记录,将过滤后的所有记录的经纬度坐标构成
- 介绍fastjson 1.2.0之后的版本支持JSONPath。,可以在java框架中当作json对象查询语言(OQL)来使用。常用APIp
- Java接口回调产生接口回调的场景在现实生活中,产生接口回调的场景很简单,比如我主动叫你帮我做一件事,然后你做完这件事之后会通知我,&quo
- 一、 代码块的概念在探究对象初始化顺序之前,我们先通过代码来了解一下代码块的概念。class Test{ public stat
- 生产者消费者模式的几种实现方式拿我们生活中的例子来说,工厂生产出来的产品总是要输出到外面使用的,这就是生产与消费的概念。在我们实际的软件开发
- MyBatis的前身叫iBatis,本是apache的一个开源项目, 2010年这个项目由apache software foundatio