1.String的基本特性

  • 声明为final,不可被继承

  • 实现Serializable接口,表示其支持序列化

  • 实现Comparable接口,表示其可比较大小

  • JDK8内部使用final char[] value存储字符串数据;JDK9改为byte[](节约空间,因为大部分String对象都是Latin-1字符,此类字符只需要一个byte存储)

  • 不可变的字符序列,下面三种情况都需要重新指定内存区域赋值,不能使用原有的value进行赋值:

    • 当字符串重新赋值时
    String s1 = "psj";
    String s2 = "psj";
    s2 ="psw";
    System.out.println(s1==s2);  // false
    
    • 当对现有的字符串进行拼接时,需要重新指定内存区域赋值,不能使用原有的value进行赋值
    String s1 = "psj";
    String s2 = "psj";
    s2 += "2"
    System.out.println(s1);  // psj
    System.out.println(s2);  // psj2
    
    • 使用replace方法修改字符串时
  • 通过字面量给字符串赋值(即String a = "psj")时,字符串值声明在堆中的字符串常量池中

String s1 = "psj";
String s2 = s1.replace('j', 'w')
System.out.println(s1);  // psj
System.out.println(s2);  // psw
  • 字符串常量池不会存储相同内容的字符串(因为字符串常量池是一个固定大小的Hashtable)。同时为了减少Hash冲突(当放入字符串常量池的String较多时,导致链表过长,进而String.intern时性能下降),可以加大StringTable的长度

2.String的内存分配

  • 对于8种基本数据类型和String类型,为了让它们运行速度更快且节省内存,都提供了常量池(类似于Java系统级别的缓存,基本数据类型的常量池由系统协调)
  • JDK6及之前,字符串常量池放在永久代(分配内存小,垃圾回收频率低);JDK7/8放置在堆(分配内存大,垃圾回收频率高)

3.字符串拼接操作

  • 常量和常量的拼接结果放置在常量池中(编译期优化)
// 源代码
public void test1(){String s1 = "a" + "b";String s2 = "ab";System.out.println(s1 == s2);  // trueSystem.out.println(s1.equals(s2));  // true
}
// 编译后的字节码文件
public void test1() {String s1 = "ab"; String s2 = "ab";System.out.println(s1 == s2);System.out.println(s1.equals(s2));
}
  • 拼接时只要有一个是变量,结果就保存在非常量池部分的堆空间中
public void test2() {String s1 = "javaEE";String s2 = "hadoop";String s3 = "javaEEhadoop";String s4 = "javaEE" + "hadoop";  // 编译期优化String s5 = s1 + "hadoop";  // 拼接中出现变量,则相当于在堆中new String()String s6 = "javaEE" + s2;  // 拼接中出现变量,则相当于在堆中new String()String s7 = s1 + s2;  // 拼接中出现变量,则相当于在堆中new String()System.out.println(s3==s4);  // trueSystem.out.println(s3==s5);  // falseSystem.out.println(s3==s6);  // falseSystem.out.println(s3==s7);  // falseSystem.out.println(s5==s6);  // falseSystem.out.println(s5==s7);  // falseSystem.out.println(s6==s7);  // false// intern():判断字符串常量池中是否存在"javaEEhadoop"// 存在则返回常量池中该字符串的地址// 不存在则在常量池中加载一份该字符串并返回该对象地址String s8 = s6.intern();  System.out.println(s3==s8);  // true
}

变量拼接原理是StringBuilder(JDK8)

// 源代码:
public void test3() {String s1 = "a";String s2 = "b";String s3 = "ab";String s4 = s1 + s2;System.out.println(s3 == s4);  // false
}
// 字节码文件:对于String s4 = s1 + s2
StringBuilder s4 = new StringBuilder();
s4.append("a");
s4.append("b");
s4.toString();  // 相当于new String("ab"),这个对象在非字符串常量池部分的堆空间中

​ 注意:只有变量的拼接才是使用StringBuilder,常量或常量引用的拼接依旧使用编译期优化

public void test4() {final String s1 = "a";final String s2 = "b";String s3 = "ab";String s4 = s1 + s2;System.out.println(s3 == s4);  // true(s1和s2相当于常量)
}

​ 循环进行拼接操作时,直接创建一个StringBuilder对象进行append操作比先创建一个String s = ""再使用加号进行拼接要快许多:

​ 后者需要创建多个StringBuilder进行append操作,并且还要进行new String(...)操作

​ 后者创建对象较多,占用空间大,GC时间长


4.intern()的使用

  • 如果不是使用双引号声明的String对象,可调用intern方法,调用后会从字符串常量池中查询当前字符串是否存在,不存在就将该字符串放入常量池
// 以下两种方式保证变量s指向的是字符串常量池中的数据
String s = "psj";
String s = new String("psj").intern();

  • 确保字符串在内存中只有一份拷贝,可以节省内存,加快字符串操作的执行速度
  • JDK6和JDK7/8/11对于intern方法使用上的区别:针对常量池中没有目标字符串对象时
    • JDK6会复制一份对象
    • JDK7及之后会复制对象的引用地址(假设堆中已经创建了该对象,则引用堆中的对象)
String s = new String("1");  // s指向堆空间
s.intern();  // 调用该方法前,字符串常量池中已经存在"1"
String s2 = "1";
System.out.println(s == s2);  // false,因为一个是堆空间中的对象,一个是字符串常量池中的对象String s3 = new String("1") + new String("1");  // s3是堆中的地址,可以理解为new String("11"),反正最后调用toString()(不针对JDK11)
// 上述拼接代码并没有在字符串常量池中生成"11"(参考面试题3)
// *执行该行代码时:
// JDK6:常量池会创建新对象"11"
// JDK7之后:常量池不会创建"11",而是创建一个对象指向堆中生成的对象(节省空间)
s3.intern();
String s4 = "11";  // s4使用的是上一行代码执行后在常量池生成"11"的地址(该"11"在JDK6中是常量池中的,在JDK7及之后是指向堆中创建的"11"对象的地址,s3始终是指向堆中创建的"11"对象的地址)
System.out.println(s3 == s4);  // JDK7之前为false,之后为true

String s3 = new String("1") + new String("1");  // 还是相当于在堆中new String("11")
String s4 = "11";  // 在字符串常量池中会生成对象"11"
s3.intern();  // 检查到在常量池中已经有"11",不创建"11"
System.out.println(s3 == s4);  // false

注意:比如new String("a") + new String("b"),在JDK7及以上最后会调用toString()方法,该方法在字符串常量池中是没有生成"ab"的


面试题

1.

public class Test{  String str = new String("good");char[] ch = {'t', 'e', 's', 't'};public void change(String str, char[] ch){str = "test ok";ch[0] = 'b';}public static void main(String[] args) {Test t = new Test();t.change(t.str, t.ch);System.out.println(t.str);  // good(不会变为test ok)System.out.println(t.str);  // best(第一个字符进行了修改)}
}

2.字符串拼接操作中的示例

3.

String s = new String(“psj”)会创建几个对象?两个

new String(“p”) + new String(“s”)创建几个对象?JDK11中创建了4个,JDK8中创建了5个(考虑最后toString方法就是6个)

注意:这种方式在字符串常量池中最终是没有"ps"对象的,如果使用new String(“ps”)是会在常量池中有"ps"的

String s = new String("a") + new String("b");  // 拼接的方式不会在常量池中生成"ab"
String s2 = s.intern();  // JDK6会复制对象,JDK7及以上复制对象的引用
System.out.println(s == "ab");
System.out.println(s2 == "ab");

在JDK6中是true和false:

在JDK7及以上是true和true:

String x = "ab";
String s = new String("a") + new String("b");  // 还是在堆中创建"ab"对象
String s2 = s.intern();  // 因为第一行代码执行后已经在常量池中生成了"ab",所以s2直接执行常量池中已有的对象即可
System.out.println(s == "ab");
System.out.println(s2 == "ab");

在所有JDK版本都是false和true:


JVM内存和垃圾回收-12.String Table相关推荐

  1. jvm内存与垃圾回收重点总结

    文章目录 一.jvm简介 1.jvm的位置 2.JVM的整体结构 3.java代码执行流程 二.类加载子系统 1.类的加载过程 2.类加载器分类 ⭐3.双亲委派机制 三.运行时数据区及线程 四.程序计 ...

  2. Java进阶 JVM 内存与垃圾回收篇(一)

    JVM 1. 引言 1.1 什么是JVM? 定义 Java Vritual Machine - java 程序的运行环境(Java二进制字节码的运行环境) 好处 一次编译 ,到处运行 自动内存管理,垃 ...

  3. JVM——内存与垃圾回收

    JVM--java virtual machine java虚拟机就是二进制字节码运行的环境 特点: 一次编译,导出运行 自动内存管理 自动垃圾回收 文章目录 JVM--java virtual ma ...

  4. JVM内存与垃圾回收篇——JVM与Java体系结构

    前言 作为Java工程师的你曾被伤害过吗?你是否也遇到过这些问题? 运行着的线上系统突然卡死,系统无法访问,甚至直接OOM! 想解决线上JVM GC问题,但却无从下手. 新项目上线,对各种JVM参数设 ...

  5. JVM内存与垃圾回收篇——堆

    堆 堆的核心概念 堆针对一个JVM进程来说是唯一的,也就是一个进程只有一个JVM,但是进程包含多个线程,他们是共享同一堆空间的. 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域. J ...

  6. 深入浅出JVM内存模型+垃圾回收算法

    文章目录 前言 JVM内存模型 1. 程序计数器(记录当前线程) 2. Java栈(虚拟机栈) 3. 本地方法栈 4. 堆 5.方法区 6.直接内存 JVM垃圾回收 垃圾判断标准 1. 引用计数法 2 ...

  7. JVM:内存与垃圾回收篇

    文章目录 **1.Java虚拟机** 1.Java虚拟机 Java虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行. 特点 一次编译,到处运行 自动内存管 ...

  8. JVM内存与垃圾回收篇——直接内存

    一.直接内存 Direct Memory介绍 直接内存不是虚拟机运行时数据区的一部分,也不是<Java虚拟机规范>中定义的内存区域,直接内存是在Java堆外的,直接向系统申请的内存区间.来 ...

  9. JVM内存和垃圾回收-02.类加载子系统

    1.内存结构概述 2.类加载子系统作用 负责从文件系统或者网络中加载Class文件(该文件在文件开头有特定的标识) 只负责Class文件的加载,至于能否运行由Execution Engine决定 加载 ...

最新文章

  1. php判断外链,php检查字符串中是否有外链的方法
  2. 四六级成绩查询,你的『验证码』刷出来了吗?
  3. 根据url提取网站域名的方法小结
  4. Java文本预处理 去除非法字符
  5. css3动画保持状态不变
  6. NSArray 数组
  7. 远程 导数据 mysql_mysql远程导入
  8. 达梦数据库(DM7试用版)安装
  9. 【网络技术题库整理4】IP地址规划技术
  10. 编写递归算法,计算二叉树叶子结点的数目。
  11. liunx下用xshell实现 密钥 + 密码 登录
  12. Python基础简答题
  13. 腾讯云轻量服务器搭建,腾讯云轻量服务器配置系统镜像自定义建站及安全组配置...
  14. 网络安全技术第四章——身份认证技术(身份认证及方式、身份认证三要素、身份认证协议、KERBEROS协议、SSL协议)
  15. CososCreator (Android)-AppLovin MAX 广告聚合平台接入+Firebase统计
  16. rust 安装与学习所遇到的部分问题
  17. 力天创见排队客流统计
  18. PTA1025C语言解析
  19. vue+elementUi+dialog封装自定dialog部分属性,并打包成插件、npm install mj-dialog --save、display、justify、between
  20. 大龄计算机考研 考研帮,过了40还想考研吗?大龄研究生讲述真实感受

热门文章

  1. 计算机子网掩码作用,什么是子网掩码 子网掩码的作用是什么?
  2. VxRail Cluster Expansion
  3. switch default多次触发
  4. K-means(K均值原型聚类)
  5. DPDK内存管理二:初始化
  6. 【HTML教程(一)】HTML标签、模板和实例
  7. 3ds Max 基于PC系统的3D建模渲染和制作软件
  8. AxureRP(中英文版)——初识Axure(一)
  9. 2.Paper小结——《Privacy-preserving blockchain-based federated learning for traffic flow prediction》
  10. 基于上下文的业务流建模法(三)