1:String 数据结构

1-1:String 为什么是 final 的?

  1. 为了实现字符串池
  2. 为了线程安全
  3. 为了实现 HashCode 的不变性

1-2:String 的内部属性

String 内部其实就是一个 char 数组。

1
private final char value[];

1-3:String 的常用方法

  1. charAt()
  2. indexOf()
  3. replace()
  4. subString()

1-4:subString 原理

通过传入的参数构建一个新的 String 对象并返回这个对象。

1
2
3
4
5
6
7
8
9
10
public String subString(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

而这个构造方法底部其实就是通过复制创建一个新数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}

1-5:String 长度有限制的

  • 编译期的限制:字符串的 UTF8 编码值的字节数不能超过 65535,字符串的长度不能超过 65534。
  • 运行时限制:字符串的长度不能超过 2^31-1,占用的内存数不能超过虚拟机能够提供的最大值。

2:String str = new String(“abc”);创建了几个对象

这段代码将创建 1 或 2 个字符串对象。

  • 如果池中已存在字符串常量 abc,则只会在堆空间创建一个字符串对象 s1,s1 内部的 char value[] 则指向常量池中的 abc。
  • 如果池中没有字符串常量 abc,那么它将首先在池中创建 abc 对象,然后在堆空间中创建 s1 对象,s1 指向堆中 new 的对象,而 s1 内部的 char value[] 则指向常量池中的 abc。因此将创建总共 2 个字符串对象。
    所以 s1 指向堆内存,s2 指向常量池,但是 s1 的 value[] 和 s2 的 value[] 指向的是常量池中的同一个对象。所以 s1 == s2 为 false,s1.equals(s2) 为 true。

2-1:String str=”abc”,堆和常量池中的情况

会在常量池中创建对象,不会创建在堆中。

3:String 的==与 equals 问题

== 的作用:

  • 基本类型:比较值是否相等。
  • 引用类型:比较内存地址值是否相等。

equals 的作用:

  • 引用类型:默认情况下,比较内存地址值是否相等。可以按照需求逻辑,重写对象的 equals 方法。

3-1:两个 st1 = “abc”相等问题

1
2
3
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); // true

因为用这种方式创建,这两个字符其实指向的是一个引用。

3-2:一个创建对象,一个 str1=“abc”

1
2
3
4
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); // false
System.out.println(str1.equals(str2)) // true

因为创建对象时引用指向堆内存中的对象,而使用 equals 会比较对象内部对字符串的引用

3-3:一个 a+b+c,一个 abc

1
2
3
String str1 = "a"+"b"+"c";
String str2 = "abc";
System.out.println(str1==str2); // true

其实 str1 只会创建一个对象,因为编译器进行了优化。会自动处理为”abc”。

3-4:一个 str1+c,一个 abc

1
2
3
4
5
String str1 = "ab";
str1 = str1+"c";
String str2 = "abc";
System.out.println(str1==str2); // false
System.out.println(str1.equals(str2)); // true

因为 String 中+的重载其实是转为 StringBuilder.append()添加再通过 toString()转为 String 类型。而 StringBuilder 的 toString()方法如下:

1
2
3
4
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}

相当于用 new 方式创建一个 String 对象,那么这两种比较结果就可以看出来。

3-5:两个 new String 对象

1
2
3
4
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1==str2); // false
System.out.println(str1.equals(str2)); // true

创建两个对象,比较引用肯定是 false,但是内部数组指向的都是常量池中的同一数据,所以 equals 为 true。

4:拼接方式

20210314121500

5: String、StringBuffer 和 StringBuilder 区别

String 不可变,StringBuffer 和 StringBuilder 可变。而 StringBuffer 线程安全,StringBuilder 线程不安全。

5-1:StringBuffer 如何实现线程安全

StringBuffer 的方式为使用synchronized修饰方法。

5-2:处理数据量较大的字符串用 String 还是 StringBuilder,为什么?

StringBuilder,因为 String 的各种拼接其实就是转化为 StringBuilder 来进行。

5-3:为什么 StringBuffer 和 StringBuilder 比 String 更快(不变性)

  1. String 类设计成 final 类型,每次有修改操作时,都会赋值给新的对象。
  2. 因为赋值给新的对象,原来的对象就不再引用,就会进行回收。

6:应用

6-1:如何把一段逗号分割的字符串转换成一个数组?

1
char[] arr = s.split(",");

6-2:String 和 char[] 数组谁更适合存密码

String,因为 String 不可变,所以不会对密码进行修改。