JAVA中的字符串常量池使用操作代码

目录
  • 前言
  • 理解字符串常量池
  • 字符串拼接方式
  • 妙用String.intern() 方法
  • 字符串常量池有多大?
  • 字符串常量池的优缺点
    • 字符串池的优点
    • 字符串池的缺点
  • 总结

前言

研究表明,Java堆中对象占据最大比重的就是字符串对象,所以弄清楚字符串知识很重要,本文主要重点聊聊字符串常量池。Java中的字符串常量池是Java堆中的一块特殊存储区域,用于存储字符串。它的实现是为了提高字符串操作的性能并节省内存。它也被称为String Intern PoolString 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 PoolString非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。

使用-XX:StringTablesize可设置StringTable的长度

  • 在jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTable Size设置没有要求
  • 在jdk7中,StringTable的长度默认值是60013StringTable Size设置没有要求

● 在jdk8中,设置StringTable长度的话,1009是可以设置的最小值

字符串常量池的优缺点

字符串池的优点

  • 提高性能。由于 JVM 可以返回对现有字符串对象的引用而不是创建新对象,因此使用字符串池时字符串操作更快。
  • 共享字符串,节省内存。字符串池允许您在不同的变量和对象之间共享字符串,通过避免创建不必要的字符串对象来帮助节省内存。

字符串池的缺点

  • 它有可能导致性能下降。从池中检索字符串需要搜索池中的所有字符串,这可能比简单地创建一个新的字符串对象要慢。如果程序创建和丢弃大量字符串,则尤其如此,因为每次使用字符串时都需要搜索字符串池。

总结

其实在 Java 7 之前,JVM将 Java String Pool 放置在PermGen空间中,它具有固定大小——它不能在运行时扩展,也不符合垃圾回收的条件。在PermGen(而不是堆)中驻留字符串的风险是,如果我们驻留太多字符串,我们可能会从 JVM 得到一个OutOfMemory错误。从 Java 7 开始,Java String Pool存放在Heap空间,由 JVM进行垃圾回收。这种方法的优点是降低了OutOfMemory错误的风险,因为未引用的字符串将从池中删除,从而释放内存。

现在通过本文的学习,你该知道如何更好的创建字符串对象了吧。

到此这篇关于JAVA中的字符串常量池的文章就介绍到这了,更多相关java字符串常量池内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java字符串split方法的坑及解决

    目录 Java字符串split方法的坑 Java字符串split方法的探究 总结 Java字符串split方法的坑 先来看几行简单的Java代码,如下: System.out.println("1,2".split(",").length); System.out.println("1,2,".split(",").length); System.out.println("".split(",&q

  • Java 常量池详解之字符串常量池实现代码

    目录 1.字符串常量池(String Constant Pool) 1.1:字符串常量池在Java内存区域的哪个位置? 1.2:字符串常量池是什么? 1.3 字符串常量池生成的时机? 如何将String对象放入到常量池 String 对象代码案例解析 new string(“abc”)创建了几个对象 解析public native String intern() 方法 Integer 对象代码案例解析 为啥Integer i1 =10 跟Integer.valueOf(10) 是相等的? 为啥I

  • Java计算字符串公式的方式解读

    目录 如何计算字符串公式 解决方案一 解决方案二 总结 如何计算字符串公式 解决方案一 使用 commons-jexl3 jar 包 可以使用 commons-jexl3 jar包,此 jar 包提供了一些方法用于计算字符串中的公式. maven 依赖如下: <dependency>     <groupId>org.apache.commons</groupId>     <artifactId>commons-jexl3</artifactId&g

  • java正则匹配读取txt文件提取特定开头和结尾的字符串

    目录 前言 一.使用FileInputStream处理 二.使用正则开始匹配 1.匹配规则书写 2.pattern 代码案例 总结 前言 前天刚入职的算法同事,过来问我怎么提取txt文件中的数据,我一看这还不简单,结果…搞了好久. 正则不用真的会忘记,写篇博客增加一下记忆吧. 需求:提取txt文件中,有特定开头(双引号) ,特定结尾(双引号) 的中间的数据,打印出来 一.使用FileInputStream处理 FileInputStream:是java中的字节输入流,就是通过字节的形式进行读取

  • Java 中的字符串常量池详解

    Java中的字符串常量池 Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new String("droid");,这两种方式我们在代码编写时都经常使用,尤其是字面量的方式.然而这两种实现其实存在着一些性能和内存占用的差别.这一切都是源于JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池.

  • Java中的字符串常量池详细介绍

    Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new String("droid");,这两种方式我们在代码编写时都经常使用,尤其是字面量的方式.然而这两种实现其实存在着一些性能和内存占用的差别.这一切都是源于JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池. 工作原理 当代码中出现字

  • Java中的常用输入输出语句的操作代码

    一.概述 输入输出可以说是计算机的基本功能.作为一种语言体系,java中主要按照流(stream)的模式来实现.其中数据的流向是按照计算机的方向确定的,流入计算机的数据流叫做输入流(inputStream),由计算机发出的数据流叫做输出流(outputStream). Java语言体系中,对数据流的主要操作都封装在java.io包中,通过java.io包中的类可以实现计算机对数据的输入.输出操作.在编写输入.输出操作代码时,需要用import语句将java.io包导入到应用程序所在的类中,才可以

  • Java中保证线程顺序执行的操作代码

    只要了解过多线程,我们就知道线程开始的顺序跟执行的顺序是不一样的.如果只是创建三个线程然后执行,最后的执行顺序是不可预期的.这是因为在创建完线程之后,线程执行的开始时间取决于CPU何时分配时间片,线程可以看成是相对于的主线程的一个异步操作. public class FIFOThreadExample { public synchronized static void foo(String name) { System.out.print(name); } public static void

  • Java字符串常量池示例详解

    为什么会有常量池的概念? 不知道小伙伴们是否有思考过这个问题? 没有思考也无所谓,小编在这里类比一下,大家就会清晰了.什么是池? 我们听的最多的池,应该是数据库连接池. 为什么会有数据库连接池,其实就是为了节省资源,提高性能,防止重复创建连接,避免占用内存和网络资源. 常量池其实就是跟数据库连接池的目的都是一样的.那么他是如何实现的呢? 因为常量池是JVM的概念,源码我们也不好看,所以我们还以连接池来类比. 池化的目标就是缓存和管理 稍微提一点池化的概念,其实就是对资源做一个包装,在包装层来加一

  • Java String 字符串常量池解析

    作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串常量池: 字符串常量池的设计意图是什么? 字符串常量池在哪里? 如何操作字符串常量池? 字符串常量池的设计思想 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能 JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化 为字符串开辟一个字符串

  • Java String类的理解及字符串常量池介绍

    目录 一. String类简介 1. 介绍 2. 字符串构造 二. 字符串常量池(StringTable) 1. 思考? 2. 介绍和分析 3. intern方法 三. 面试题:String类中两种对象实例化的区别 四. 字符串的不可变性 一. String类简介 1. 介绍 字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串. Java的String类在lang包里,java.lang.String是java字符串类,包含

  • Java中的字符串用法小结

    本文实例总结了Java中的字符串用法.分享给大家供大家参考.具体分析如下: 字符串的本质是char类型的数组,但在java中,所有用双引号""声明的字符串都是一个String类的对象.这也正体现了Java完全面向对象的语言特点. String 类 1.String类对象表示的是一个常量字符串.它是不可变长度的.也就是说,一旦创建了一个String类的实例,那么这个实例所表示的串是不可改变的.类似于 str = str + "Hello"; 这样的操作,实质上是将 s

  • Java class文件格式之常量池_动力节点Java学院整理

    常量池中各数据项类型详解 常量池中的数据项是通过索引来引用的, 常量池中的各个数据项之间也会相互引用.在这11中常量池数据项类型中, 有两种比较基础, 之所以说它们基础, 是因为这两种类型的数据项会被其他类型的数据项引用. 这两种数据类型就是CONSTANT_Utf8 和 CONSTANT_NameAndType , 其中CONSTANT_NameAndType类型的数据项(CONSTANT_NameAndType_info)也会引用CONSTANT_Utf8类型的数据项(CONSTANT_Ut

随机推荐