字符串

1 String 底层实现

String 被声明为 final,因此它不可被继承。

底层是 char 或 byte 类型的 value 数组,value 数组也被声明为 final,这意味着数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

编辑器可让字符串共享在常量池。

1.1 Java 8 - char 数组

在 Java 8 中,String 内部使用 char 数组存储数据。

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}

1.2 Java 9 - byte 数组

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;

/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}

1.3 不可变的好处

  • 可以缓存 hash 值
    • 因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
  • String Pool 的需要
    • 如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
  • 安全性
    • String 经常作为参数,String 不可变性可以保证参数不可变。
    • 例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。
  • 线程安全
    • String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

2 StringBuilder 和 StringBuffer

2.1 可变性与线程安全

  • String 不可变,因此是线程安全的
  • StringBuilder 可变,不是线程安全的,效率比 StringBuffer 高
  • StringBuffer 可变,是线程安全的,内部使用 synchronized 进行同步

2.2 底层实现

StringBuilder 和 StringBuffer 继承了 AbstractStringBuilder,AbstractStringBuilder 的 char 数组没有 final 关键字修饰,字符数组长度可变,所有 StringBuilder 和 StringBuffer 也是可变的

AbstractStringBuilder

AbstractStringBuilder(int capacity) {
value = new char[capacity];
}

StringBuilder

public StringBuilder() {
// StringBuilder 类继承 AbstractStringBuilder 抽象类
// 创建长度 16 的字符数组
super(16);
}
// 字符串拼接
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}

3 String Pool 与引用

字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。

在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

  • new String():会在堆新建对象
  • intern() 方法: String 对象的 intern 方法会得到字符串对象在常量池中对应的版本的引用,如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用;
  • “”字面量:使用字面量的形式创建字符串,会自动地将字符串放入 String Pool 中
String s1 = "Programming";//先去常量池取,没有就新建对象放在常量池
String s2 = new String("Programming");//两个字符串对象,一个是常量池的"Programming",一个是用 new 创建在堆上的对象
String s3 = "Program";//常量池
String s4 = "ming";//常量池
String s5 = "Program" + "ming";//常量池
String s6 = s3 + s4;//堆
System.out.println(s1 == s2);//false
System.out.println(s1 == s5);//true
System.out.println(s1 == s6);//false
System.out.println(s1 == s6.intern());//true
System.out.println(s2 == s2.intern());//false

4 String 常用方法

4.1 初始化

1)使用字符串常量直接初始化
String s = "hello!";
2)使用构造方法创建并初始化
String s = new String(Object);

初始化源码

private final char value[];
// 本质是字符数组常量,所以不可变
public String() {
this.value = "".value;
}

4.2 操作

1)截取字符串
  • 单点截取:subString(开始下标)
  • 双点截取:subString(开始下标,结束下标)
2)拼接字符串
  • + 号
  • join:用一个定界符分割,String.join(“定界符”,“待合并字符串”…)

字符串的 + 操作其本质是创建了 StringBuilder 对象进行 append 操作,然后将拼接后的 StringBuilder 对象用 toString 方法处理成 String 对象

一般情况进行字符串拼接用 + 就可以,但是如果是循环拼接,则需要用 StringBuilder 的 append 来实现。

若不使用 StringBuilder 的 append 方法而使用 + 来进行连接。那么每次在循环体内都将会在 Heap 中创造一个新的 String 对象,造成资源浪费。

3)获取信息
  • 下标:indexOf(子字符),lastIndexOf(子字符)
  • 字符:charAt(下标)
  • 字节数组:getBytes()
  • 字符数组:toCharArray()
  • 长度:length()
4)替换字符串
  • 去掉前后空格:trim()
  • 子字符串:split(字符串),StringTokenizer()
5)判断字符串
  • 是否相等:.equals()
  • 不区分大小写:.equalsIgnoreCase()
  • 是否为空串:.length()==0 或 .equals(“”)
  • 是否为null:== null
  • 前缀:startsWith(前缀)
  • 后缀:endsWith(后缀)
  • 大小:compareTo()
6)字符串转换
  • 大/小写:
    • 大写:toLowerCase()
    • 小写:toUpperCase()
  • 字符串转换为基本类型
    • Long.parseLong(“1231”);
    • Double.parseDouble(“0.213”);
  • 基本类型转换为字符串
    • 基本数据类型变量 + “”
    • String.valueOf(其他类型的参数);
Author: iMine
Link: https://imine141.github.io/2020/08/16/Java%E5%9F%BA%E7%A1%80/%E5%9F%BA%E7%A1%80%E8%AE%BE%E8%AE%A1/%E5%AD%97%E7%AC%A6%E4%B8%B2/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.