Java字符串常量池用法及说明(StringTable)

 更新时间:2026年06月27日 08:50:50   作者:不应该热爱  
这段文章详细介绍了Java中的字符串处理,包括字符串的不可变性、拼接方式及其效率问题,以及字符串常量池的应用,通过代码示例解释了String对象的创建和引用机制,强调了使用`intern`方法将字符串加入常量池的重要性

前言:

在介绍字符串常量池之前,我们先来简单了解下Java中字符串的概念以及常见的一些问题.

一、字符串

Java字符串其实只是Unicode字符序列,Java本身没有内置的字符串类型,而是在标准Java类库中提供了一个预定类,很自然的叫做String,每一个双引号引起来的字符串都是String类的一个实例:

        String s = "";//空的字符串
        String str = "Hello";

1.1 子串

提到子串,那就免不了介绍以下subString这个方法了,该方法的功能为:从一个较大的字符串中提取一个子串。

  • 其一为,subString(x,y):(注意这里是左闭右开区间) 所以该方法是截取下标x~y-1 的字符串。
  • 其二为,subString(x) : 该方法是截去前x个字符串后的字符串。

具体情况如下代码:

ps:subString 的工作方式有一个优点,容易计算子串的长度,比如:字符串s.subString(a,b) 的长度就为b-a。 

1.2 拼接

1.2.1 字符串与字符串拼接

与绝大多数设计语言一样,Java语言允许使用+号来连接两个字符串。

下述代码将“hello”赋值给了s3变量:

1.2.2 字符串与非字符串拼接

当将一个字符串与非字符串的值进行拼接的时候,后者会转换成字符串,例如:

 此特性其实我们经常在打印时候用到:

注意:这里是字符串与非字符拼接并不是字符与非字符拼接:

如果是后者的话;可能会转变成数字,例:

1.3 字符串的不可变性

String类中没有提供修改字符串中某个字符的方法,如果希望将”hello“修改为“help!” ,只能提取想要保存的字符串,再与希望替换的字符串拼接,比如:

分析:

由于不能修改Java字符串中的某个字符,所以Java我、文档中将String类称之为不可变的,不过可以修改s指向的值,让它引用另外的字符串。 

可能有人会说,拼接字符串这样的做法是否会降低运行效率呢?

答案其实并不确定,虽然通过拼接的方法,让s2指向了"help!"看上去有些低效,但是不可变字符串却有一个优点:编译器可以让字符串共享。

原因:

 通过观察源码可知,String类的value是被private修饰的,类外是拿不到这个值的

 可能很多人以为String类不可变是因为value[] 这个数组被final修饰,但其实并不是这样的,因为value存储的是一个引用,而不是常量。

下面用代码诠释一下:

我们发现,arr[1]的值的确被改为了c。准确的说 这里的final限制的是arr指向的值不能改变,也就是不能重新指向一个新的数组对象。

总结:

  1. String类被final修饰,表明该类不能被继承
  2. value被修饰被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。

所以一些设计字符串修改的函数,都是创建一个新的对象,然后修改新的对象。 

注意  += 这种的拼接操作,也不是在String类自身上进行的,而是通过中间创建许多临时变量而完成的,效率很低下。于是引入了StringBuffer和StringBuilder。

 以下代码可以看出String类在拼接时的效率比StringBuffer和StringBuilder低:

public static void main(String[] args) {
        long start = System.currentTimeMillis();
        String s = "";
        for(int i = 0; i < 1_0000; ++i){
            s += i;
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        start = System.currentTimeMillis();
        StringBuffer sbf = new StringBuffer("");
        for(int i = 0; i < 1_0000; ++i){
            sbf.append(i);
        }
        end = System.currentTimeMillis();
        System.out.println(end - start);
        start = System.currentTimeMillis();
        StringBuilder sbd = new StringBuilder();
        for(int i = 0; i < 1_0000; ++i){
            sbd.append(i);
        }
        end = System.currentTimeMillis();
        System.out.println(end - start);
    }

1.3.1为什么设计成不可变的

为什么 String 要涉及成不可变的?(不可变对象的好处是什么?) 

  1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
  2. 不可变对象是线程安全的.
  3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.

1.4 判断字符串是否相等

可以使用equals方法来检测字符串是否相等,形如:s.equals(r); 如果字符串s与字符串r相等,则返回true,否则返回false。

需要注意的是:s与t可以是字符串常量,也可以是字符串字面量(用双引号引起来的部分)。

比如以下的语句也是合法的:

🔔🔔千万不要使用 == 来判断两个字符串是否相等,这个运算符只能确认两个字符串是否处在同一位置,虽然,字符串在同一位置上,它们必然相等, 但是完全可能将内容相同的多个字符串放在不同的位置上。

例如:

如果虚拟机始终将相同的字符串共享,就可以使用 == 运算符来检测是否相等,但实际上字符串字面量是共享的,而+ 或 substring 等操作得到的字符串并不共享,因此千万不能使用==来判断字符串的相等性,以免出现bug。 

二、字符串常量池

字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(一种高效用来进行查找的数据结构,后序给大家详细介绍),不同JDK版本下字符串常量池的位置以及默认大小是不同的:

JDK版本字符串常量池位置大小设置
Java6(方法区)永久代固定大小:1009
Java7堆中可设置,没有大小限制,默认大小:60013
Java8堆中可设置,有范围限制,最小是1009

2.1 字符串常量池的应用

2.1.1 再谈String对象创建

大家可能会好奇,为什么s1,s2指向的是同一个对象,而s3,s4指向的对象却不相同呢?

在Java程序中,类似于:1, 2, 3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快、更节省内存,提供了一个String类型的常量池,只要是双引号引起来的对象,都会存放在常量池中。

 分析:因为s1,s2分别都是被双引号引起来的对象,所以都会被存储到常量池中,可能有人会问,为什么s2并没有单独在常量池中存储一份而是引用了s1所指向的对象呢?

那是因为被引号引起来的对象,会先到常量池中寻找是否已经存在相同的内容,如果存在相同内容,那么就直接引用它,就不需要再创建一个对象了(这也是为什么常量池效率高的原因)。

现在我们再来看几组代码;

代码1:

 我们可以发现,s1,s2,s3都引用了同一个数组对象,这里s3不与s1,s2相同的原因是因为,s3是通过new了一个String对象,而String对象里面的value值存储的是hello的地址值。

代码2:

下面难度加大一点:

小插曲:

这里需要注意的是s1的new string对象中存放的是数组,根据源码我们可以得知,是将这个ch数组拷贝一份再存入string对象中的。

 但是我们只要加入intern这个方法,就可以让其相等。

原因:intern 是一个native方法(Native方法指,底层使用C++实现的,看不到其实现的源代码)

该方法会检查常量池中是否有这个对象,如果无,则入池(入池并不会将自身销毁,而是在常量池中添加这个对象),有的话,则返回该对象(并不会再次入池了)。

如果将这个顺序一调换,就输出false。

原因也很简单,因为已经在常量池中存在这个”abc“了,不会再将s1的string对象的abc再次入池。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringMVC4.3 HttpMessageConverter接口实现源码分析

    SpringMVC4.3 HttpMessageConverter接口实现源码分析

    这篇文章主要为大家介绍了SpringMVC4.3 HttpMessageConverter接口实现源码分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • java中判断String类型为空和null的几种方法

    java中判断String类型为空和null的几种方法

    判断一个字符串是否为空或者为null是一个常见的操作,本文主要介绍了java中判断String类型为空和null的几种方法,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • 使用log4j MDC实现日志追踪

    使用log4j MDC实现日志追踪

    这篇文章主要介绍了使用log4j MDC实现日志追踪方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Spring的组合注解和元注解原理与用法详解

    Spring的组合注解和元注解原理与用法详解

    这篇文章主要介绍了Spring的组合注解和元注解原理与用法,结合实例形式详细分析了spring组合注解和元注解相关功能、原理、配置及使用方法,需要的朋友可以参考下
    2019-11-11
  • Spring Cloud超详细i讲解Feign自定义配置与使用

    Spring Cloud超详细i讲解Feign自定义配置与使用

    这篇文章主要介绍了SpringCloud Feign自定义配置与使用,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • Java中获取Class对象的3种方式代码示例

    Java中获取Class对象的3种方式代码示例

    Class对象是反射的核心,通过他可以调用类的任意方法,下面这篇文章主要给大家介绍了关于Java中获取Class对象的3种方式,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-07-07
  • 详解SpringBoot容器的生命周期

    详解SpringBoot容器的生命周期

    在使用SpringBoot进行开发时,我们经常需要对Spring容器的生命周期进行了解和掌握,本文将介绍SpringBoot容器的生命周期,包括容器的创建、初始化、销毁等过程,并提供相应的代码示例
    2023-06-06
  • springboot logback调整mybatis日志级别无效的解决

    springboot logback调整mybatis日志级别无效的解决

    这篇文章主要介绍了springboot logback调整mybatis日志级别无效的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • java通过snmp协议获取物理设备信息

    java通过snmp协议获取物理设备信息

    这篇文章主要介绍了java通过snmp协议获取物理设备信息,snmp中文含义是简单网络管理协议,可用完成对计算机、路由器和其他网络设备的远程管理和监视,本文我们是通过java程序来获取,需要的朋友可以参考下
    2023-07-07
  • 使用Netty搭建服务端和客户端过程详解

    使用Netty搭建服务端和客户端过程详解

    这篇文章主要介绍了使用Netty搭建服务端和客户端过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07

最新评论