您当前的位置: 首页 >  面试

white camel

暂无认证

  • 2浏览

    0关注

    442博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

第 12 章 字符串常量池 (String Table)、intern()方法、String的内存结构、相关面试题

white camel 发布时间:2021-01-16 22:20:56 ,浏览量:2

文章目录
  • 第 12 章 StringTable
    • 1、String 的基本特性
      • 1.1、String 概述
      • 1.2、String 的基本特征
      • 1.3、String 的底层结构
    • 2、String 的内存分配
      • 2.1、String 内存分配演进过程
      • 2.2、为什么要调整 String 位置 (从永久代调到堆)
    • 3、String 的基本操作
    • 4、字符串拼接操作
      • 4.1、符串拼接操作的结论
      • 4.2、字符串拼接的底层细节
    • 5、intern() 的使用
      • 5.1、intern() 方法的说明
      • 5.2、new String() 的说明
      • 5.3、有点难的面试题
      • 5.4、intern() 方法的总结
      • 5.5、intern() 方法的练习
      • 5.6、intern() 方法效率测试
    • 6、StringTable 的垃圾回收
    • 7、G1 中的 String 去重操作

第 12 章 StringTable 1、String 的基本特性 1.1、String 概述

String 的概述

  • String:字符串,使用一对“”引起来表示
    String s1 = "guizy" ;   			// 字面量的定义方式
    String s2 =  new String("moxi");     // new 对象的方式
    
  • String声明为final的,不可被继承
  • String实现了Serializable接口:表示字符串是支持序列化的。实现了Comparable接口:表示String可以比较大小
  • String在jdk8及以前内部定义了final char[] value用于存储字符串数据。JDK9时改为byte[]

为什么 JDK9 改变了 String 的结构

官方文档

http://openjdk.java.net/jeps/254

为什么改为 byte[] 存储?

在许多不同的应用程序收集数据表明: 字符串是堆使用的主要组成部分, 大多数字符串对象只包含拉丁字符, 只需要用到一个字节的空间, 之前用char[]数组, 站2个字节的空间, 实际用到只有一个字节。

  • byte[] 数组 + 编码标志位的方式
  1. String类的当前实现将字符存储在char数组中,每个字符使用两个字节(16位)。
  2. 从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分而且大多数字符串对象只包含拉丁字符。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部char数组中有一半的空间将不会使用。
  3. 之前 String 类使用 UTF-16 的 char[] 数组存储现在改为 byte[] 数组 外加一个编码标志位存储,该编码标志将指定 String 类中 byte[] 数组的编码方式
  4. 结论:String再也不用char[] 来存储了,改成了byte [] 加上编码标记,节约了一些空间
  5. 同时基于String的数据结构,例如StringBufferStringBuilder也同样做了修改
// 之前
private final char value[];
// 之后
private final byte[] value
1.2、String 的基本特征

String 的基本特征

String:代表不可变的字符序列。简称:不可变性。

  1. 对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
  2. 对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  3. 调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  • 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。

当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。

代码

@Test
public void test1() {
    String s1 = "abc";//字面量定义的方式,"abc"存储在字符串常量池中
    String s2 = "abc";
    System.out.println(s1 == s2);// true, 表示指向字符串常量池中的同一地址
    
    s1 = "hello";
    System.out.println(s1 == s2);//判断地址:false, 表明s1从新开辟了一个存储空间

    System.out.println(s1);//hello
    System.out.println(s2);//abc
}
  • 字节码指令
    • 取字符串 “abc” 时,使用的是同一个符号引用:#2
    • 取字符串 “hello” 时,使用的是另一个符号引用:#3
 0 ldc #2 
 2 astore_1
 3 ldc #2 
 5 astore_2
 6 ldc #3 
 8 astore_1
 9 getstatic #4 
12 aload_1
13 aload_2
14 if_acmpne 21 (+7)
17 iconst_1
18 goto 22 (+4)
21 iconst_0
22 invokevirtual #5 
25 getstatic #4 
28 aload_1
29 invokevirtual #6 
32 getstatic #4 
35 aload_2
36 invokevirtual #6 
39 return
  • 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值

  • 代码

@Test
public void test2() {
    String s1 = "abc";
    String s2 = "abc";
    s2 += "def";
    System.out.println(s2);//abcdef
    System.out.println(s1);//abc
}
  • 字节码指令:拼接操作(+)通过StringBuilder 的 append() 方法完成
 0 ldc #2 
 2 astore_1
 3 ldc #2 
 5 astore_2
 6 new #7 
 9 dup
10 invokespecial #8 
13 aload_2
14 invokevirtual #9 
17 ldc #10 
19 invokevirtual #9 
22 invokevirtual #11 
25 astore_2
26 getstatic #4 
29 aload_2
30 invokevirtual #6 
33 getstatic #4 
36 aload_1
37 invokevirtual #6 
40 return
  • 调用string的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
@Test
public void test3() {
    String s1 = "abc";
    String s2 = s1.replace('a', 'm');
    System.out.println(s1);//abc
    System.out.println(s2);//mbc
}

来看看 replace() 方法的源码

  • new String(buf, true); 后,返回新的 String 对象
public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */

        while (++i             
关注
打赏
1661428283
查看更多评论
0.0453s