Marvel-Site Marvel-Site
首页
  • Java

    • Java基础
    • Java进阶
    • Java容器
    • Java并发编程
    • Java虚拟机
  • 计算机基础

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • Linux
  • 框架|中间件

    • Spring
    • MySQL
    • Redis
    • MQ
    • Zookeeper
    • Git
  • 架构

    • 分布式
    • 高并发
    • 高可用
    • 架构
  • 框架

    • React
    • 其他
  • 实用工具
  • 安装配置

    • Linux
    • Windows
    • Mac
  • 开发工具

    • IDEA
    • VsCode
  • 关于
  • 收藏
  • 草稿
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)

Marvel

吾必当乘此羽葆盖车
首页
  • Java

    • Java基础
    • Java进阶
    • Java容器
    • Java并发编程
    • Java虚拟机
  • 计算机基础

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • Linux
  • 框架|中间件

    • Spring
    • MySQL
    • Redis
    • MQ
    • Zookeeper
    • Git
  • 架构

    • 分布式
    • 高并发
    • 高可用
    • 架构
  • 框架

    • React
    • 其他
  • 实用工具
  • 安装配置

    • Linux
    • Windows
    • Mac
  • 开发工具

    • IDEA
    • VsCode
  • 关于
  • 收藏
  • 草稿
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)
  • Java

    • Java基础

    • Java进阶

      • 类的初始化与对象的初始化
      • Object类
      • 深度理解字符串String
        • 1 介绍
        • 2 创建对象
          • String的不可变性
          • String对象的创建
          • String的拼接
          • 字符串常量池
        • 3. 常用方法
          • intern()
          • equals()
        • 4. 不可变性
        • 4 常见问题
          • String、StringBuffer、StringBuilder对比
          • 字符串拼接
      • Integer创建对象分析
      • 从原理上分析 i = i++ 与 i = ++i
      • Comparable和Comparator的区别
      • 四种引用介绍
      • 静态代理与动态代理
      • 位运算
    • Java容器

    • Java并发编程

    • Java虚拟机

    • 常见面试题

  • 计算机基础

  • 框架|中间件

  • 架构

  • 后端
  • Java
  • Java进阶
Marvel
2022-07-13
目录

深度理解字符串String

# 深度理解字符串String

# 1 介绍

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
    ...
}
1
2
3
4
  • 实现了 Serializable接口,表示字符串支持序列化
  • 实现了 Comparable接口,表示字符串可以比较大小
  • 定义 final char value[],代表不可变的字符序列
  • 用 final 修饰 class,代表类不可以被继承

# 2 创建对象

# String的不可变性

通过字面量创建对象,此时的字符串值声明在字符串常量池中。

String s1 = "abc";
String s2 = "abc";

s2 = "def";
String s3 = s1 + "123";
String s4 = s1.replace('a', 'c');
1
2
3
4
5
6

image-20220817121028315

注意:为了简化叙述,上图没有将「堆」画出来。

字符串常量池中不会存储相同内容的字符串。

  • 当对字符串重新赋值时,不会修改字符串常量池中原有的字面量。
  • 当对字符串进行连接操作时,也不会修改字符串常量池中原有的字面量。
  • 当调用 String 的 replace() 方法修改指定的字符或字符串时,也不会修改字符串常量池中原有的字面量。

# String对象的创建

两种方式:

  1. 通过字面量定义:字面量声明在方法区的字符串常量池中;
  2. 通过构造器:对象在堆中开辟空间,字面量声明在方法区的字符串常量池中。

测试代码:

String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");

System.out.println(s1 == s2);   // true
System.out.println(s1 == s3);   // false
System.out.println(s3 == s4);   // false
System.out.println(s1.equals(s2));  // true
System.out.println(s1.equals(s3));  // true
System.out.println(s3.equals(s4));  // true
1
2
3
4
5
6
7
8
9
10
11

==:比较的是引用对象的地址值。

equals:String 重写了 equals() 方法,比较的是字符串的值。

image-20220817121130489

所以,通过 String s3 = new String("abc")方式创建了几个对象呢?

当字符串常量池中存在“abc”字面量时,创建一个对象,堆中的字符串对象。

当字符串常量池中不存在"abc"字面量时,创建两个对象,一个是堆中的字符串对象,另一个是 char[] value 对应的常量池中的字面量。

# String的拼接

测试代码

String s1 = "abc";
String s2 = "123";

String s3 = "abc123";
String s4 = "abc" + "123";
String s5 = s1 + "123";
String s6 = "abc" + s2;
String s7 = s1 + s2;
String s8 = s7.intern();

System.out.println(s3 == s4);  // true
System.out.println(s3 == s5);  // false
System.out.println(s3 == s6);  // false
System.out.println(s5 == s6);  // false
System.out.println(s5 == s7);  // false
System.out.println(s3 == s8);  // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

当常量与常量拼接的时候结果还是在常量池,且常量池不会存在相同内容的常量;

当其中一个是变量,相当于自动调用了构造器的方法创建对象,拼接的结果将在堆中;

如果拼接的结果调用 intern() 方法,返回值就是在常量池中。

image-20220817121156359

# 字符串常量池

字符串常量池是 JVM 为了提升性能和减少内存消耗,针对于字符串专门开辟的一块区域,主要是为了避免字符串被重复创建。

JDK1.6:字符串常量池在方法区中

JDK1.7:字符串常量池在堆中

JDK1.8:字符串常量池在方法区中,但是此时方法区的实现为元空间。

# 3. 常用方法

# intern()

String::intern() 是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此 String 对象的字符串,则返回代表池中整个字符串的 String 对象的引用;否则,会将此String对象包含的字符串添加到常量池,并且返回此 String 对象的引用。

String s1 = new StringBuffer("re").append("dis").toString();
System.out.println(s1 == s1.intern()); // true

String s2 = new StringBuffer("ja").append("va").toString();
System.out.println(s2 == s2.intern()); // false
1
2
3
4
5

为什么 Java 不同呢?因为有一个初始化的 Java字符串(JDK自带),在加载 sun.misc.Version 这个类的时候进入常量池。

System 类中有 initializeSystemClass 方法,该方法调用 sun.misc.Version.init(),Version 这类中有常量 Java。

# equals()

equals() 是 Object 类的方法,它用来比较对象的内存地址。

Object 类是一切类的父类,所以 String 内也包含equals() 方法并且重写了这个方法,用于比较两个字符串的字符是否相同。

# 4. 不可变性

⭐ 不可变性的定义

对象一旦被创建后,对象所有的状态及属性在其生命周期内不会发生任何变化。

这就意味着,一旦我们将一个对象分配给一个变量,就无法再通过任何方式更改对象的状态了。

⭐ 不可变的原因




 



public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    ...
}
1
2
3
4
5
6
  1. 用 final 修饰 String 这个类,不可被继承,防止被他人继承后修改
  2. 用 final 修饰 char 数组,数组的引用无法被修改,并且内部没有提供修改 char 数组的方法。
  3. 在源码里避免修改 char 数组,并且 char 数组,对 String 的修改操作都会创建一个新的 String 对象。

⭐ 设计为不可变的原因

  1. 设计为不可变才能使用字符串常量池。如果可变的话,当一个字符串的内容被修改后,其他相同的字符串都会被修改,因为他们引用了字符串常量池中的同一个字面量。
  2. 安全问题,String 是最常用的数据类型,String 被许多 Java 类库用来作为参数,如果 String 不是固定不变的,将会引起各种安全隐患。

# 4 常见问题

# String、StringBuffer、StringBuilder对比

StringBuffer 与 StringBuilder 都是继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中使用字符数组保存字符串,但是没有用 final 和 private 关键字修饰,最关键的是 AbstractStringBuilder 类还提供了很多修改字符串的方法,比如append方法。

StringBuffer 与 StringBuilder 的构造方法都是调用父类构造方法,也就是 AbstractStringBuilder 实现的。

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    char[] value;
    int count;

    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
}
1
2
3
4
5
6
7
8

线程安全性:

  • String:对象是不可变的,线程安全。
  • StringBuffer:对方法加了同步锁或调用的方法加了同步锁,所以是线程安全的。
  • StringBuffer:并没有对方法进行同步加锁,所以是非线程安全的。

性能:

  • String:每次改变String都会创建一个新的String对象,然后指针指向新的String对象。
  • StringBuffer:每次对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。
  • StringBuilder:相比StringBuffer的性能提升10%-15%,但线程不安全。

总结:

  • 操作少量数据:String
  • 单线程操作字符串缓冲区下大量数据:StringBuilder
  • 多线程操作字符串缓冲区下大量数据:StringBuffer

# 字符串拼接

+ 拼接方法,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个String对象。在循环中使用 + 进行字符串的拼接的时候,编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。

所以,在循环中拼接对象的时候一定要在循环开始前创建一个 StringBuilder 对象。

编辑 (opens new window)
#Java
上次更新: 2023/08/20, 21:21:52
Object类
Integer创建对象分析

← Object类 Integer创建对象分析→

最近更新
01
位运算
05-21
02
二叉树
05-12
03
Spring三级缓存解决循环依赖
03-25
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Marvel
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式