概念
- Java字符串就是Unicode字符序列
- 每个用双引号括起来的字符串都是String类型
子串
-
substring方法
-
String s1 = "hello"; // 返回位置0到位置3的子串,不包括位置3,3-0即子串的长度 String s2 = s1.substring(0,3); // 返回从位置1到字符串尾部的子串 String s3 = s1.substring(1);
拼接
-
+号连接字符串
-
当其中一个操作数是非字符串类型时,会自动转换成字符串类型
-
int x=1,y=2,z=3; System.out.println("x+y+z="+x+y+z); System.out.println("x+y+z="+(x+y+z)); //Output x+y+z=123 x+y+z=6 -
使用+拼接字符串时,底层会创建StringBuilder对象,调用append方法拼接字符串,最后调用toString方法将StringBuilder对象转换为字符串对象
-
两个字符串拼接时
-
public static void test3(){ String res = "a"+"b"; } // 等价于 public static void test4(){ StringBuilder res = new StringBuilder(); res.append("a"); res.append("b"); String s = res.toString(); }- 此时,StringBuilder对象内部的byte数组为默认容量16,因此,在拼接两个总长度大于16的字符串时,需要扩容(创建新的byte数组,将原数组元素复制到新数组中)。
- 如果直接创建StringBuilder对象连接字符串,可以在调用构造方法时指定byte数组初始容量,减少后续的扩容次数,提高效率。
-
-
多个字符串拼接时
-
public static void test1(){ String res = ""; for(int i=0;i<10;i++) res += "a"; } // 等价于 public static void test2(){ String res = ""; for(int i=0;i<10;i++) res = new StringBuilder().append(res).append("a").toString(); }- 可见,在循环中拼接多个字符串时,会创建多个StringBuilder对象,浪费内存空间并且降低效率。
-
如果直接创建StringBuilder对象
-
public static void test5(){ StringBuilder res = new StringBuilder(10); for(int i=0;i<10;i++) res.append("a"); String s = res.toString(); }- 只需创建一个StringBuilder对象,并且可以通过指定初始容量,避免调用append方法时由于byte数据容量不够导致的扩容操作,从而提高效率
-
-
-
总结,Java字符串拼接使用StringBuilder类的append方法的效率要高于"+"
-
-
concat方法
-
"a".concat("b")- 连接两个字符串
- 底层是创建一个byte数组,分别将两个字符串复制进该数组中,之后根据该byte数组重新创建一个String对象并返回。
- 在拼接两个字符串时
- concat方法是创建一个byte数组,即分配一次内存空间,将两个字符串复制进该数组,然后根据该byte数组创建String对象
- StringBuilder的append方法也是创建一个byte数组,即分配一次内存空间,然后将两个字符串复制进该数组中,最后需要调用toString方法返回字符串对象,该方法会把两个字符串再复制一次
- 综上,拼接两个字符串时,concat方法效率更高,但是拼接多个字符串时,concat每次都需要分配内存空间,而StringBuilder可以指定容量减少扩容次数,此时,StringBuilder的append方法效率高
-
-
-
join方法
-
// 第一个参数为界定符 String s3 = String.join(" ","hello","world","Java"); // output hello world Java
-
-
repeat方法
-
// 指定字符串重复多次 String s3 = "Java".repeat(2); // output JavaJava
-
不可变字符串
-
Java中,String对象不可修改,但是可以让String类型变量引用指向另外一个String对象
-
String s1 = "hello"; s1 = "world"; String s = "x"; s += "y"; // s = "xy",s由字符串"x"指向了字符串"xy"
-
-
优点
- 原始字符串存在堆中,编译器可以让字符串共享
检测字符串是否相等
-
equals方法
-
String s1 = "hello"; s1.equals("hello"); // true
-
-
注意
-
不要通过"=="判断字符串是否相等
-
"==",比较的是两个字符串变量指向的内存地址是否相等
-
"hello"=="hello"; // true,因为指向同一块内存 "hello".substring(0,3)=="hel"; // false,因为不指向同一块内存 "hello".substring(0,3).equals("hel") // true,字面量相等
-
-
空串与null串
- 空串与null串不同
- 空串是Java对象,串长度为0,内容为空
- null表示目前没有任何对象与该变量关联
码点与代码单元
-
码点是指与一个编码表中的某个字符对应的代码值。Unicode标准中,码点采用十六进制书写,并加上前缀"U+",如"U+0041"表示A
-
每个字符用16位表示,称为代码单元
-
length方法返回给定字符串所需要的代码单元数量,codePointCount返回自己码点数量
-
String s1 = "\uD835\uDD46"; System.out.println(s1); // 𝕆 System.out.println(s1.length()); // 2 System.out.println(s1.codePointCount(0,s1.length())); // 1
-
String API
-
// 返回字符串代码代码单元个数 int length(); // 判断字符串是否为空 boolean isEmpty(); // 返回给定位置的代码单元 char charAt(int index); // 判断两个字符串是否相等,区分大小写 boolean equals(Object anObject); // 判断两个字符串是否相等,不区分大小写 boolean equalsIgnoreCase(String anotherString); // 按字典顺序比较两个字符串大小,小于返回负数,大于返回正数,等于返回0 int compareTo(String anotherString); // 字符串是否以prefix开头 boolean startsWith(String prefix); // 字符串是否以suffix结尾 boolean endsWith(String suffix); // 返回与字符串str匹配的第一个子串的开始位置 int indexOf(String str); // 返回与字符串str匹配的最后一个子串的开始位置 int lastIndexOf(String str); // 返回给定位置的子串,包括beginIndex,不包括endIndex String substring(int beginIndex, int endIndex); // 删除原字符串头部和尾部小于等于U+0020(空格)的字符 String trim(); // 删除原字符串头部和尾部空格 String strip(); // 指定类型数据转换为字符串对象 String valueOf(指定类型);-
String.valueOf方法传入null会产生空指针异常,因为直接传入null会调用以下方法
-
public static String valueOf(char[] data) { return new String(data); }-
会让char数组data=null,之后在调用构造方法时
-
public String(char[] value) { this((char[])value, 0, value.length, (Void)null); } -
value.length会导致空指针异常
-
-
-
-
String.valueOf方法传入一个null对象则不会产生空指针异常,返回字符串对象"null"
-
Object tmp = null; String a = String.valueOf(tmp); // a = "null"
-
-
构建字符串
-
StringBuilder(Java 5引入)
-
非线程安全
-
创建StringBuilder对象
- StringBuilder底层通过创建一个byte数组(Java 9之后)作为字符串缓冲区。
- 创建StringBuilder对象时,byte数组默认容量为16,可以自己指定容量,如果在创建StringBuilder对象时,将String对象作为参数,则创建的byte数组容量为默认容量16加上String对象长度
-
append方法
-
该方法用于向StringBuilder对象追加字符串
-
public AbstractStringBuilder append(String str) { if (str == null) { return this.appendNull(); } else { int len = str.length(); this.ensureCapacityInternal(this.count + len); this.putStringAt(this.count, str); this.count += len; return this; } }-
首先判断字符串是否为null,如果是null,则在byte数组后加上四个字符 'n', 'u', 'l', 'l'
-
否则,计算字符串的长度,然后调用ensureCapacityInternal方法检查StringBuilder对象的原byte数组的容量能不能装下新的字符串,如果装不下则对byte数组进行扩容
-
int oldCapacity = this.value.length >> this.coder; int newCapacity = (oldCapacity << 1) + 2; if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; }- 首先让新容量为原容量的2倍+2,如果新容量仍然不够,则直接让新容量等于原字符串与追加字符串的总长度
-
扩容会将原字符串复制到新的byte数组中
-
-
之后,将追加字符串复制到byte数组中,并且计算新的字符串长度
-
至此,append方法追加字符串结束
-
-
由于append方法会判断当前byte数组容量是否足够,所以如果要追加多个字符串,应该判断大致容量,并且创建StringBuilder对象时指定该容量,减小扩容的次数,提高效率
-
-
delete方法
- delete(int start, int end)可以删除指定位置的字符,如果end超过字符串总长度,则end=count,即会删除start之后所有的字符。
- delete方法底层调用的是System.arraycopy方法,即删除元素是让数组中end之后的元素移位,覆盖start到end之间的元素
-
-
StringBuffer
- 线程安全(方法上用synchronized修饰),但效率较低
- StringBuffer和StringBuilder的父类都是抽象类AbstractStringBuilder