字符串操作是计算机程序设计中最常见的行为。String对象是不可改变的,从JDK文档中可以看出,每一个对字符串进行修改的操作都会创建一个全新的对象,而原始对象仍然存在。以下是我对字符串基础知识的一些了解
重载"+"与StringBuilder
- 当用"+"拼接的都是字符串字面量和基本类型字面量时,该表达式等价于拼接后的字符串,以下两个表达式是等价的
String str1 = "Hello" + " world" + 47; String str2 = "Hello world47";
- 若表达式中含有变量,如下
String str = ""; String s = str + "Hello" + "world";
编译器会自动创建StringBuilder对象,并调用append方法对字符串拼接,最后调用toString方法创建字符串对象并返回String对象的引用赋值给s。可以通过javap -c class文件名命令来反编译.class文件,以下是输出结果的一部分
- 若遇到需要循环进行多次"+"操作时,如下
String a = "a"; for (int i = 0; i < 100000; i++) a += "a";
这段代码的执行效率很低,因为循环一次就会创建一个StringBuilder对象并调用toString方法来创建String对象,所以执行过程中需要创建大量的对象。想要提高效率可以通过手动创建StringBuilder对象来对字符串进行拼接,如下
StringBuilder sb = new StringBuilder("a"); for (int i = 0; i < 100000; i++) sb.append("a"); String s = sb.toString();
- 编码中可能会出现一些无意识的递归,如下
class Test { public String toString() { return "Test: " + this; } }
由于编译器看到字符串后面跟了一个"+",于是编译器会将this转为字符串即调用toString方法,以致于出现递归调用toString方法。若真想拼接上该对象的地址可以使用super.toString()来代替
String的一些常用API
- length:返回字符串的字符数量
String s = "Hello world"; int length = s.length(); System.out.println(length); // 11
- charAt:返回某个索引位置上的字符
char c = s.charAt(6); // s在上段代码中 System.out.println(c); // w
- getChars、getBytes:将字符串中的一串字符复制到指定数组中
char[] cs = new char[5]; s.getChars(0, 5, cs, 0); System.out.println(cs); // Hello
- toCharArray:将字符串拆分成字符后存入char数组中
char[] cs = s.toCharArray(); System.out.println(cs); // Hello world
- equals、equalsIgnoreCase:比较两个字符串的内容是否一致,后者忽略大小写
System.out.println(s.equals("hello world")); // false System.out.println(s.equalsIgnoreCase("hello world")); // true
- compareTo:通过每个字符对应的Unicode值来比较大小
System.out.println(s.compareTo("Hello World")); //32 System.out.println('w' - 'W'); // 32
- contains:判断是否包含参数的内容
System.out.println(s.contains("Hello")); // true
- contentEquals:判断是否与参数的内容一致
System.out.println(s.contentEquals("Hello")); // false
- regionMatcher:判断该字符串中某段是否与参数字符串中的某段相同,有个忽略大小写的重载方法
System.out.println(s.regionMatches(6, "world", 0, 5)); // true
- startsWith、endsWith:判断是否以某个字符串开头、结尾
System.out.println(s.startsWith("He")); // true System.out.println(s.endsWith("ld")); // true
- indexOf、lastIndexOf:返回第一个、最后一个某个字符的索引位置,若没有返回-1
System.out.println(s.indexOf('o')); // 4 System.out.println(s.lastIndexOf('o')); // 7 System.out.println(s.indexOf('h')); // -1
- subString:截取字符串并返回截取出来的字符串
System.out.println(s.substring(6)); // world System.out.println(s.substring(0, 5)); // Hello
- concat:拼接字符串
System.out.println(s.concat("!!!!")); // Hello world!!!!
- replace:替换所有要替换的字符串为新的字符串,并返回替换后的字符串
System.out.println(s.replace('l', 'L')); // HeLLo worLd
- toLowerCase、toUpperCase:将字符串转为全大、小写
System.out.println(s.toLowerCase()); // hello world System.out.println(s.toUpperCase()); // HELLO WORLD
- trim:删除两端的空白字符,并返回删除后的字符
String trim = " Hello world ".trim(); System.out.println(trim); // Hello world System.out.println(trim.length()); // 11
- intern:若字符串常量池中有该对象则返回常量池中对象的引用,若没有则直接返回该对象的引用,例子请看后面
字符串对象的存储位置
- 程序中字符串字面量的对象存储在常量池中
String s = "Hello world";
- 通过new关键字创建的字符串对象存储在堆中
String s = new String("Hello world");
以上代码实际上是创建了两个对象,一个在常量池中一个在堆中,可以通过以下代码进行验证
String s = new String("Hello world"); System.out.println(s.intern() == s); // false
由于常量池中存在该字符串对象,所以intern返回的是常量中的对象的引用,而s是堆中对象的引用,所以最终输出fals。但有的人错误的认为通过new创建出来的字符串就一定会有存在两个对象(可能只有我之前是这么认为的0.0),如下代码所示
String str = new String(new char[]{'H', 'e', 'l', 'l', 'o'}); System.out.println(str.intern() == str); //true