字符串
- Java的字符串是在
java.lang包中,所以可以直接使用,无需引包。
- Java 8之前的String是用char数组实现的。即
"abc"的底层实现为{'a', 'b', 'c'}。
- 从Java 9开始,改为使用byte数组实现。
- 字符串字面量实际是String类的实例(相当于自动给你new了一个String,假设为s1,但是如果第二次用这个字面量初始化s2的话,就不会重新new了,而是直接把s1的对象引用给s2,详见
==)。
- String对象一旦创建完毕,该对象内部其中的字符内容不可以修改。
- 但是String对象指针的指向可以更改。
内存位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Main { public static void main(String[] args) { String s = "555"; s += "555"; s = "666"; test(s); System.out.println(s); } static void test(String str) { str = str + "555"; } }
|
String字符串都是new出来的,因此都是在堆空间中。





字符串常量池 (String Constant Pool, SCP)
- 字符串常量池。是指:从Java 7开始,属于堆空间的一部分。(以前是放在方法区中的)
- 遇到字符串字面量时,会去查SCP。
- 如果SCP中匹配到与该字面量内容一致的字符串对象A时,则返回A。
- 否则创建一个新的对象D,加入到SCP中,返回D。
1 2 3
| String s1 = "xcg"; String s2 = "xcg"; System.out.println(s1 == s2);
|
1 2 3 4 5 6 7 8
| String s1 = "xcg"; String s2 = new String("xcg"); String s3 = new String(s1); String s4 = new String(s2); char[] cs = {'x', 'c', 'g'}; String s5 = new String(cs); String s6 = new String(s5); System.out.println(s1 == s2);
|
传入常量字符串的new底层:
1 2 3 4 5 6 7
| new String("xcg"); ---------------------- public String(String original) { this.value = original.value; this.hash = original.hash; }
|
"xcg"的底层空间是只有一份的,在SCP中。而new String("xcg")本质上只是新创建了一个String对象,value的拷贝只是把旧地址给了新String中的value指针。即两个value数组是同一个地址的数组。这属于浅拷贝。

因此,s1到s4只是String对象地址不同,但是,内部的value数组是同一个。

传入字符数组的new底层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| char[] cs = {'x', 'c', 'g'}; new String(cs); --------------------------------- public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } --------------------------------- public static char[] copyOf(char[] original, int newLength) { char[] copy = new char[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
|
可以看到,用字符数组去new一个String的话,底层要深拷贝一个数组,即产生一个新数组的地址赋给String对象内部的value指针。

intern方法
1
| public native String intern();
|
被native关键字修饰,代表不是用Java编写的,可能是C语言。返回一个字符串。
作用是:如果SCP中存在一个与B内容一样的字符串对象A,则返回A;否则,将B加入到SCP中,返回B。
看下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class static void main(String[] args) { int a = 1, b = 2, c = 3; String s1 = String.format("%d%d%d", a, b, c); String s2 = String.format("%d%d%d", a, b, c); System.out.println(s1 == s2); String s3 = s1.intern(); String s4 = s2.intern(); System.out.println(s1 == s3); System.out.println(s1 == s4); String s5 = "123"; System.out.println(s1 == s5); }
|
s1不等于s2,因为通过format生成的字符串不是字符串常量!因此,s1和s2指向的字符数组不是字符串常量池中的变量。
去SCP观察有没有重复串的情况仅限于涉及到字符串字面量的情况,或者用另一个字符串对象直接构造新的。或者:直接调用String类的intern方法,这是手动去观察SCP有无重复串。但是要记住了,intern有一个隐形操作:”否则,将B加入到SCP中“!
因此,在s1调用了intern之后,s1的内容"123"就放入了常量池中。一旦intern被调用过一次,那么之后s2的intern返回的就是常量池中的地址了。
因此,s3==s4。
至于s5,因为是用字符串字面量初始化的,所以隐形调用了intern,所以实际指向的也是s1的value地址。
我们可以推测出,两个字符串直接==判断是去判断内部成员的value地址是否一样。即判断是否是浅拷贝。
字符串常用方法
去除左右空格 - trim
去除首左、尾右的空格
contains
是否包含目标串(字符串匹配)
split
将字符串按照分隔符分割为数组
compareTo
挨个比较大小
equals
查看内容是否相等
1 2 3
| String s1 = "abc"; String s2 = new String("abc"); s1.equals(s2);
|
==
如果直接用s1 == s2去比较字符串,则变成了引用之间的比较。则是去比较的是两个字符串对象的内存地址。
1 2 3 4 5 6 7 8 9 10 11
| public class Test { public static void main(String[] args) { String s1 = "123"; String s2 = "123"; String s3 = new String("123"); System.out.println(s1 == s2); System.out.println(s1 == s3); } }
|
截取子串 - subString
截取beginIndex及之后的,endIndex之前的串。左闭右开。
1 2 3 4
| int beginIndex = 2; int endIndex = 5; "xcg123go".subString(beginIndex); "xcg123go".subString(beginIndex, endIndex);
|
替换目标串 - replace
1
| "xcg123go".replace("123", "Never");
|
StringBuilder
与String相比,共同点是都实现了CharSequence接口。
在进行字符串频繁的改动时(拼接、替换等),使用StringBuilder。
1 2 3
| StringBuilder sb = new StringBuilder(); sb.append("123"); sb.append("456");
|
常用方法
append, insert, delete, replace, reverse
append原理
StringBuilder的底层是也是一个value = new char[n];的字符数组。
与String的不同是,这个字符数组有预留空间,记录length来标记数组中有效结尾。
当空间满时,动态扩容。