字符串

字符串操作是计算机程序设计中最常见的行为。

不可变 String

String 对象是不可变的,String 类中每一个看起来会修改 String 值的方法,实际上都是创建了一个全新的 String 对象,以包含修改后的字符串内容,而最初的 String 对象则丝毫未动。

重载“+” 与 StringBuilder

虽然 Java 并不允许程序员重载任何操作符,但是为 String 对象重载了 ++=

使用 + 可以很方便的连接字符串,但是也会产生一大堆需要垃圾回收的中间对象。虽然,对于 +,编译器进行了一定的优化,会自动产生 StringBuilder 用于连接。但是遇到循环时,就可能会创建过多的 StringBuilder

因此,如果字符串操作比较简单,可以使用 +,信赖编译器,它会合理地构造最终的字符串。但是,如果操作比较复杂,或者里面存在循环,那么最好自己创建 StringBuilder 对象来进行操作。

StringBuilder 是非线程安全,StringBuffer 是线程安全的,但是也意味着开销会大些。

无意识的递归

当想要打印对象的内存地址时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {

// 如果使用 return "A: " + this; 将会得到异常
// 因为这里 String 对象紧跟着 + ,this 也会被编译器当作 String 类型
// 即 this.toString() ,这便造成了递归调用
// 所以应该使用 Object 对象的 toString() 方法
// 即 super.toString()
@Override
public String toString() {
return "A: " + super.toString();
}
}

List<A> list = new ArrayList<A>();
for (int i = 0; i < 3; i++) {
list.add(new A());
}
System.out.println(list);

// [A: A@1b6d3586, A: A@4554617c, A: A@74a14482]

格式化输出

1
2
3
4
5
6
7
8
9
System.out.printf("%d %f\n", 1, 1.0);  // 1 1.000000
System.out.format("%d %f\n", 1, 1.0); // 1 1.000000

// Formatter 的构造器经过重载可以接受多种输出目的地
Formatter formatter = new Formatter(System.out);
formatter.format("%d %f\n", 1, 1.0); // 1 1.000000

// String.format() 返回一个 String 对象
System.out.println(String.format("%d %f", 1, 1.0)); // 1 1.000000

格式化常见的抽象语法为:%[argument_index$][flags][width][.precision]conversion

更加详细的说明见 java.util.Formatter - - - Java API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// conversion 表示数据类型,d 代表十进制整数
// argument_index$ 表示参数在参数列表中的位置
Formatter formatter = new Formatter(System.out);
formatter.format("%2$d %3$d %1$d\n", 1, 2, 3); // 2 3 1

// flags 表示标识集
// - 表示左对齐,0 表示用 0 来填充
// width 代表所占的宽度,默认添加空格来补充
formatter.format("[%3d]\n", 1); // [ 1]
formatter.format("%03d\n", 1); // 001

// .precision 对于小数,表示保留的小数位数
// 对于 String 对象,表示输出的最大数量
formatter.format("%.2f\n", 1.0); // 1.00
formatter.format("%.2s\n", "1234"); // 12

正则表达式

Java 中,\\ 代表正则表达式的 \,即 \\d 代表正则表达式的 \d。另外 \\\\ 代表普通的 \

更加详细的说明见 java.util.regex.Pattern - - - Java API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// String 对象的 matches() 方法
// 判断 String 对象与参数的正则表达式是否匹配

// \d 代表数字 0-9
// -? 代表拥有一个 - 或者没有 -
// + 代表拥有一个或多个之前的表达式
// \d+ 即代表拥有多位数字
// 因为 + 有特殊含义,所以 \\+ 代表普通 +
// (-|\\+)? 代表可以正号,也可以负号,也可以没有
System.out.println("1".matches("\\d")); // true
System.out.println("-2".matches("-\\d")); // true
System.out.println("3".matches("-?\\d")); // true
System.out.println("123".matches("\\d")); // false
System.out.println("123".matches("\\d+")); // true
System.out.println("+1".matches("\\+\\d")); // true
System.out.println("+1".matches("(-|\\+)?\\d")); // true
System.out.println("-1".matches("(-|\\+)?\\d")); // true
System.out.println("1".matches("(-|\\+)?\\d")); // true

// String 对象的 split() 方法
// 根据正则表达式切割 String 对象,并返回 String[]

// \W 代表非单词字符
// \w 代表单词字符, o\w 代表 o 与后面紧接着的一个单词字符,数字也算是单词字符
System.out.println(Arrays.toString("hello- world".split("\\W+"))); // [hello, world]
System.out.println(Arrays.toString("world".split("o\\w"))); // [w, ld]

// String 对象的 replaceFirst() 方法
// 根据正则表达式替换 String 对象内第一个匹配的部分
// String 对象的 replaceAll() 方法
// 根据正则表达式替换 String 对象内所有匹配的部分
System.out.println("wo5k".replaceFirst("o\\w", "-")); // w-k
System.out.println("wo5k,wo7p,woot".replaceAll("o\\w", "-")); // w-k,w-p,w-t

比起功能有限的 String 类,Pattern 对象的 matcher() 方法会生成一个 Matcher 对象,有更多功能可以使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A {

public static String string =
"Twas bryllyg, and y slythy toves\n"
+ "Did gyre and gymble in y wabe\n"
+ "All mimsy were y borogoves\n"
+ "And y mome raths outgrabe\n";

static void f() {

// 查找文章中独立的 y ,并输出起始位置
Matcher matcher = Pattern.compile("(\\s)y(\\s)").matcher(string);
while (matcher.find()) {
System.out.println(matcher.group().charAt(1) + " : " + (matcher.start() + 1));
}
}
}

A.f();

// 输出:
/*
y : 18
y : 56
y : 78
y : 94
*/

Pattern 类的 compile() 方法还可以接受标记参数,以调整匹配行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

class A {

public static String string =
"Twas bryllyg, and y slythy toves\n"
+ "and y slythy toves\n";

static void f(int n) {

Matcher matcher = null;

switch (n) {

// 由于 $ (The end of a line)的存在,不进行处理的话,得到的仅仅只是最后一个结果
case 1:
matcher = Pattern.compile("toves$").matcher(string);
break;

// (?m) Pattern.compile 的标记,启用多行模式
case 2:
matcher = Pattern.compile("(?m)toves$").matcher(string);
break;

// (?m) 与 Pattern.MULTILINE 意义相同
case 3:
matcher = Pattern.compile("toves$", Pattern.MULTILINE).matcher(string);
break;

}

while (matcher.find()) {
System.out.println(matcher.group() + " : " + matcher.start());
}
}
}

A.f(1); // toves : 46

A.f(2);

// 输出:
/*
toves : 27
toves : 46
*/

组是用括号划分的正则表达式,可以根据组的编号来引用某个组。例如:A(B(C))D(E)

  • group(0) = ABCDE
  • group(1) = BC
  • group(2) = C
  • group(3) = E
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class A {

public static String string =
"58.27.82.161@02/10/2005\n"
+ "204.45.234.40@02/11/2005\n"
+ "---------\n"
+ "204.45.234.40@02/11/2005\n";

public static void f() {

// [AB] 代表包含 A 和 B 的任何字符,和 A|B 作用相同
// X{n} 代表恰好 n 次 X,\d{2} 即恰好 2 位整数
// X{n,} 代表至少 n 次 X
// X{n,m} 代表至少 n 次 X,且不超过 m 次
String regex = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@(\\d{2}/\\d{2}/\\d{4})";

Matcher matcher = Pattern.compile(regex).matcher(string);
while (matcher.find()) {
System.out.println(matcher.group());
System.out.printf("ip: %s date: %s\n\n", matcher.group(1), matcher.group(2));
}
}
}

A.f();

// 输出:
/*
58.27.82.161@02/10/2005
ip: 58.27.82.161 date: 02/10/2005

204.45.234.40@02/11/2005
ip: 204.45.234.40 date: 02/11/2005

204.45.234.40@02/11/2005
ip: 204.45.234.40 date: 02/11/2005
*/

扫描输入

Scanner 的构造器可以接受任何类型的输入对象,包括 File 对象,String 对象等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A {

public static void f() {

// scanner 的 useDelimiter() 方法,用来设置定界符,进行分词
// useDelimiter() 方法可以接受 String 的正则表达式,也可以接受 Pattern 对象
// \s* 代表零个或多个空白符
Scanner scanner = new Scanner("11, 12, 13");
scanner.useDelimiter("\\s*,\\s*");
while (scanner.hasNext()) {
System.out.println(scanner.nextInt());
}

// delimiter() 方法返回 Pattern 对象
System.out.println(scanner.delimiter());
}
}

A.f();

// 输出:
/*
11
12
13
\s*,\s*
*/