• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Java字符串常量池StringTable

武飞扬头像
你不应该热爱
帮助1

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

参考书籍: 《Java核心技术》

参考网站:牛客

作者水平很有限,如果发现错误,麻烦及时告知作者哦!十分感谢!

目录

一、字符串

1.1 子串

1.2 拼接

1.2.1 字符串与字符串拼接

1.2.2 字符串与非字符串拼接

1.3 字符串的不可变性

1.3.1为什么设计成不可变的 

1.4 判断字符串是否相等

二、字符串常量池

2.1 字符串常量池的应用

2.1.1 再谈String对象创建

 三、面试题

一、字符串

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

  1.  
    String s = "";//空的字符串
  2.  
    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低:

  1.  
    public static void main(String[] args) {
  2.  
    long start = System.currentTimeMillis();
  3.  
    String s = "";
  4.  
    for(int i = 0; i < 1_0000; i){
  5.  
    s = i;
  6.  
    }
  7.  
    long end = System.currentTimeMillis();
  8.  
    System.out.println(end - start);
  9.  
    start = System.currentTimeMillis();
  10.  
    StringBuffer sbf = new StringBuffer("");
  11.  
    for(int i = 0; i < 1_0000; i){
  12.  
    sbf.append(i);
  13.  
    }
  14.  
    end = System.currentTimeMillis();
  15.  
    System.out.println(end - start);
  16.  
    start = System.currentTimeMillis();
  17.  
    StringBuilder sbd = new StringBuilder();
  18.  
    for(int i = 0; i < 1_0000; i){
  19.  
    sbd.append(i);
  20.  
    }
  21.  
    end = System.currentTimeMillis();
  22.  
    System.out.println(end - start);
  23.  
    }
学新通

学新通

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再次入池。

学新通

 三、面试题

 面试题:请解释String类中两种对象实例化的区别
JDK1.8中(该题的前提是:常量池不存在hello)
1. String str = "hello"
只会开辟一块堆内存空间,保存在字符串常量池中,然后str共享常量池中的String对象
2. String str = new String("hello")
会开辟两块堆内存空间,字符串"hello"保存在字符串常量池中,然后用常量池中的String对象给新开辟
的String对象赋值。
3. String str = new String(new char[]{'h', 'e', 'l', 'l', 'o'})
现在堆上创建一个String对象,然后利用copyof将重新开辟数组空间,将参数字符串数组中内容拷贝到
String对象中

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhifbegh
系列文章
更多 icon
同类精品
更多 icon
继续加载