Java字符串常量池用法及说明(StringTable)
前言:
在介绍字符串常量池之前,我们先来简单了解下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指向的值不能改变,也就是不能重新指向一个新的数组对象。

总结:
- String类被final修饰,表明该类不能被继承
- 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 要涉及成不可变的?(不可变对象的好处是什么?)
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
- 不可变对象是线程安全的.
- 不可变对象更方便缓存 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接口实现源码分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-09-09
Spring Cloud超详细i讲解Feign自定义配置与使用
这篇文章主要介绍了SpringCloud Feign自定义配置与使用,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2022-06-06
springboot logback调整mybatis日志级别无效的解决
这篇文章主要介绍了springboot logback调整mybatis日志级别无效的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2020-10-10


最新评论