第13章 字符串
13.1 不可变String
public class Test {
public static void main(String[] args) {
String a = "abc";
String b = f(a);
System.out.println(a);
System.out.println(b);
}
public static String f(String s){
return s.toUpperCase();
}
}
/*
abc
ABC
*/
String类型是不可变的,向方法中传递的参数,是指向String对象的一个引用;而对方法而言,参数是传递信息用的,而不是让该方法改变自己的。
13.2 重载“+”与StringBuilder
String对象是不可变的,指向它的任何引用都不能改变它的值,但它也会造成一些效率问题。
# 生成JVM字节码(可以方便的查看代码的底层原理)
javap -c class文件
+ 底层是创建StringBuilder对象,使用append()方法,连接字符串,其存在的问题是当处于循环状态时,每次循环都会创建一个StringBuilder对象,因此,速度会变慢。
public class Test {
public static void main(String[] args) {
String s = "a";
for (int i = 0; i < 10; i++) {
s += "b";
}
}
}
Compiled from "Test.java"
public class com.ding.five.Test {
public com.ding.five.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String a
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: bipush 10
8: if_icmpge 37
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<
init>":()V
18: aload_1
19: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: ldc #6 // String b
24: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: invokevirtual #7 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
30: astore_1
31: iinc 2, 1
34: goto 5
37: return
分析: 从第8行到34行构成了循环体,每次都会新建一个StringBuilder对象。
public class Test {
public static void main(String[] args) {
StringBuilder s = new StringBuilder("a");
for (int i = 0; i < 10; i++) {
s.append("b");
}
}
}
Compiled from "Test.java"
public class com.ding.five.Test {
public com.ding.five.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: ldc #3 // String a
6: invokespecial #4 // Method java/lang/StringBuilder."<
init>":(Ljava/lang/String;)V
9: astore_1
10: iconst_0
11: istore_2
12: iload_2
13: bipush 10
15: if_icmpge 31
18: aload_1
19: ldc #5 // String b
21: invokevirtual #6 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: pop
25: iinc 2, 1
28: goto 12
31: return
分析 在第0行创建了StringBuilder对象,而从15到28行的循环体并没有创建对象,因此,与上种方法对比,速度会更快。
+和StringBuilder对比
public static void main(String[] args) {
String s = "a";
long l = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s +"a";
}
System.out.println(System.currentTimeMillis() - l); // 120
StringBuilder stringBuilder = new StringBuilder("a");
l = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
stringBuilder.append("a");
}
System.out.println(System.currentTimeMillis() - l); // 1
}
// 可以看到后者的速度远远快于前者,但是如果没有达到一个数量级,几乎没有影响
注意:stringBuilder.append("key"+":"+"value");
千万不要这样写,编译器会为括号内的操作创建StringBuilder对象。
13.3 无意识的递归
使用this打印内存地址
public class Pet {
@Override
public String toString() {
return "内存地址:"+this;
}
}
public static void main(String[] args) {
Pet p = new Pet();
System.out.println(p);
}
/*
Exception in thread "main" java.lang.StackOverflowError
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:449)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at com.ding.five.Pet.toString(Pet.java:12)
*/
原因: 当打印对象时,会调用该对象的toString()方法,当一个对象与String类型相加时,会调用该对象的toString方法,将该对象转为String类型,因此会导致递归调用。
正确处理
public class Pet {
@Override
public String toString() {
return "内存地址:"+super.toString();
}
}
public static void main(String[] args) {
Pet p = new Pet();
System.out.println(p);
}
/*
内存地址:com.ding.five.Pet@1540e19d
*/
13.4 String上的操作
public static void stringOption() throws UnsupportedEncodingException {
byte[] bytes = {97,98,99};
char[] chars = {'a','b','c'};
// 创建String对象
String string = "abc";
string = new String(bytes);
System.out.println(string);
string = new String("abc");
string = new String(chars);
string = new String(new StringBuilder("abc"));
string = new String(new StringBuilder("abc"));
// 字符个数
int length = string.length(); // 3
// 指定索引位置的字符
char c = string.charAt(1); // b
// 获得字节数组
bytes = string.getBytes();
bytes = string.getBytes("UTF-8"); // 指定编码
// 复制到指定字符数组
string.getChars(0,2,chars,1); // [a, a, b]
// 生成一个字符数组
chars = string.toCharArray();
// 比较内容是否相同
boolean equals = string.equals("abc");
// 忽略大小写比较
boolean equalsIgnoreCase = string.equalsIgnoreCase("ABC"); // true
// 按ASCII码比较
int compareTo = string.compareTo("aeb"); // -3
// 按ASCII码忽略大小写比较
int compareToIgnoreCase = string.compareToIgnoreCase("ABC"); // 0
// 是否包含str
boolean contains = string.contains("bc"); // true
// 比较内容是否相同
boolean contentEquals = string.contentEquals("abc"); // true
contentEquals = string.contentEquals(new StringBuffer("abc")); // true
// 比较部分内容是否相同
boolean regionMatches = string.regionMatches(1, "efhbcdd", 3, 2); // true
// 忽略大小写
string.regionMatches(true,1,"efhBCdd",3,2); // true
// 起始字符串
boolean startsWith = string.startsWith("ab"); // true
// 结束字符串
boolean endsWith = string.endsWith("bc"); // true
// 字符的索引
int indexOf = string.indexOf(97); // 0
indexOf = string.indexOf("bc"); // 1
indexOf = string.indexOf(97,1); // -1
// 截取子字符串
String substring = string.substring(1); // bc
substring = string.substring(0,1); // a
// 连接字符串
String concat = string.concat("xyz");
// 替换
String replace = string.replace("b", "e"); // aec
// 改变大小写
String upperCase = string.toUpperCase(); // ABC
String lowerCase = upperCase.toLowerCase(); // abc
// 删除两端空白符
String trim = " a ".trim();
System.out.println("1"+trim+"1"); // 1a1
// 转为字符串
String valueOf = String.valueOf(1);
}
intern()方法
public static void main(String[] args) {
String s = new String("abc");
String s1 = "abc";
// 先去字符串常量池寻找,有返回引用,没有创建并返回引用
String s2 = s.intern();
System.out.println(s == s1); // false
System.out.println(s == s2); // false
System.out.println(s1 == s2); // true
}
13.5 格式化输出
public static void main(String[] args){
int x = 12;
double y = 133.2;
System.out.printf("age:%d high:%f",x,y); // age:12 high:133.200000
System.out.println();
System.out.format("age:%d high:%f",x,y); // age:12 high:133.200000
}
Formatter类
所有格式化功能都由Formatter处理。
public static void main(String[] args){
// 指定输出位置
Formatter f = new Formatter(System.out);
f.format("age:%d",12);
}
// age:12
格式化符
public class Test {
public static void main(String[] args){
Formatter formatter = new Formatter(System.out);
f(formatter,"name","high");
f(formatter,"make",127.1);
f(formatter,"makejens",122.459);
}
public static void f(Formatter f,String name,double high){
// - 左对齐(默认右),10 指定最小尺寸 .2 浮点数保留位数/string的最大输出数
f.format("%-10s%10.2f\n",name,high);
}
public static void f(Formatter f,String name,String high){
f.format("%-10s%10s\n",name,high);
}
}
/*
name high
make 127.10
makejens 122.46
*/
转换符
符号 | 说明 |
---|---|
d | 整数(十进制) |
c | Unicode字符 |
b | Boolean(非空为true,包括0) |
s | String |
f | 浮点数(十进制) |
e | 浮点数(科学计数)(1000 = 1*e3) |
x | 整数(十六进制) |
h | 散列码(十六进制) |
% | 字符“%” |
String中的格式化
String format = String.format("%d", 12);
System.out.println(format); // 12
二进制转十六进制工具
public class Test {
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("e:/Test.java");
byte[] bytes = new byte[1024];
in.read(bytes);
System.out.println(f(bytes));
in.close();
}
public static String f(byte[] bytes){
StringBuilder stringBuilder = new StringBuilder();
int n = 0;
for (byte b : bytes) {
if (n % 16 == 0){
// 位数用0补
stringBuilder.append(String.format("%05x: ",n));
}else {
stringBuilder.append(String.format("%02x ",b));
}
n++;
if (n % 16 == 0) {
stringBuilder.append("\n");
}
}
return stringBuilder.toString();
}
}
/*
00000: 75 62 6c 69 63 20 63 6c 61 73 73 20 54 65 73
00010: 20 7b 0d 0a 20 20 20 20 70 75 62 6c 69 63 20
00020: 74 61 74 69 63 20 76 6f 69 64 20 6d 61 69 6e
00030: 53 74 72 69 6e 67 5b 5d 20 61 72 67 73 29 20
00040: 68 72 6f 77 73 20 49 4f 45 78 63 65 70 74 69
00050: 6e 20 7b 0d 0a 20 20 20 20 20 20 20 20 46 69
00060: 65 49 6e 70 75 74 53 74 72 65 61 6d 20 69 6e
00070: 3d 20 6e 65 77 20 46 69 6c 65 49 6e 70 75 74
00080: 74 72 65 61 6d 28 22 65 3a 2f 54 65 73 74 2e
00090: 61 76 61 22 29 3b 0d 0a 20 20 20 20 20 20 20
000a0: 62 79 74 65 5b 5d 20 62 79 74 65 73 20 3d 20
000b0: 65 77 20 62 79 74 65 5b 31 30 32 34 5d 3b 0d
000c0: 20 20 20 20 20 20 20 20 69 6e 2e 72 65 61 64
000d0: 62 79 74 65 73 29 3b 0d 0a 20 20 20 20 20 20
*/
13.6 正则表达式
正则表达式是一种强大而灵活的文本处理工具,我们可以以编程的方式,构造复杂的文本模式;其提供了一种通用的方式对字符串进行:匹配/选择/编辑/验证。
CharBuffer/String/StringBuffer/StringBuilder都实现了CharSequence接口,都可以进行正则表达式操作。
基础
public static void main(String[] args) {
// 匹配格式
System.out.println("123".matches("\\d+"));
// 分割字符串
String string = "a1b2c3e4";
String[] strings = string.split("2");
System.out.println(Arrays.toString(strings));
strings = string.split("\\d");
System.out.println(Arrays.toString(strings));
// 2:分割出子字符串最多数
strings = string.split("\\d", 2);
System.out.println(Arrays.toString(strings));
// 替换字符
String rf = string.replaceFirst("\\d", "*");
System.out.println(rf);
String ra = string.replaceAll("\\d", "-");
System.out.println(ra);
}
/*
true
[a1b, c3e4]
[a, b, c, e]
[a, b2c3e4]
a*b2c3e4
a-b-c-e-
*/
13.6.2 创建正则表达式
字符
表达式 | 含义 |
---|---|
\t | 制表符Tab |
\n | 换行符 |
\r | 回车 |
\f | 换页 |
\e | 转义 |
字符类
表达式 | 含义 |
---|---|
. | 任意字符 |
[abc] | a或b或c |
[^abc] | 非abc字符 |
[a-zA-Z] | a-z或A-Z字符 |
[abc[hij]] | abchij任意字符 |
\s | 空白符(空格/tab/换行/换页/回车) |
\S | 非空白符 |
\d | 数字[0-9] |
\D | 非数字[0-9]或[\d] |
\w | 词字符[a-zA-Z0-9] |
\W | 非词字符[^\w] |
逻辑操作符
表达式 | 含义 |
---|---|
XY | Y跟在X后面 |
X|Y | X或Y |
(X) | 捕获组,可以在表达式中用\i引用第i个捕获组 |
边界匹配符
表达式 | 含义 |
---|---|
^ | 一行的开始 |
$ | 一行的结束 |
\b | 词的边界 |
\B | 非词的边界 |
\G | 前一个匹配的结束 |
13.6.3 量词
贪婪型: 为表达式发现尽可能多的匹配。
勉强型: 用"?"指定,匹配表达式最少字符数。
占有型: java中才可用,用“+”指定,不保存中间状态,使执行更高效。
贪婪型 | 勉强型 | 占有型 | 规则 |
---|---|---|---|
X? | X?? | X?+ | 0或1个 |
X* | X*? | X*+ | 0或多个 |
X+ | X+? | X++ | 1或多个 |
X | X? | X+ | 恰好n个 |
X{n,} | X{n,}? | X{n,}+ | 至少n个 |
X{n,m} | X{n,m}? | X{n,m}+ | n-m个 |
public static void main(String[] args) {
System.out.println(Arrays.toString("1aa23".split("a+")));
System.out.println(Arrays.toString("1aa23".split("a+?")));
/*
[1, 23]
[1, , 23]
*/
// 可以看到,贪婪型的一次匹配了两个a,而勉强型一次匹配一个a
}
13.6.4 Pattern和Matcher
基本
public static void main(String[] args) {
// 是否符合正则规则
boolean b = Pattern.matches("\\d", "1");
System.out.println(b); // true
Pattern pattern = Pattern.compile("\\d"); // 匹配规则
// 分离字符串
String[] strings = pattern.split("a1b2c");
System.out.println(Arrays.toString(strings)); // [a, b, c]
Matcher matcher = pattern.matcher("a1b2");
// 替换符合正则规则的元素
String string = matcher.replaceAll("*");
System.out.println(string); // a*b*
// 判断字符串开始的部分是否符合正则规则
boolean lookingAt = matcher.lookingAt();
System.out.println(lookingAt); // false
matcher = pattern.matcher("1a2bc5");
// 类似于迭代器,向前遍历输入字符串
while(matcher.find()){ // 注意:字符串始必须符合规则,否则无法继续
System.out.println(matcher.group());
/*
* 1
* 2
* 5
*/
}
int i = 0;
// 接收一个整数,以其作为索引向后查询最近符合正则的字符串
while(matcher.find(i)){
System.out.println(matcher.group());
/*
* 1
* 2
* 2
* 5
* 5
* 5
*/
i++;
}
}
group
public static void main(String[] args) {
Pattern pattern = Pattern.compile("\\d([a-z])");
Matcher matcher = pattern.matcher("1a,2e");
int count = matcher.groupCount();
System.out.println(count); // 1
while(matcher.find()){
// 返回前一次匹配操作
String string = matcher.group();
System.out.println(string);
// 返回前一次匹配擦作指定组的内容
string = matcher.group(0);
System.out.println(string);
// 组的起始索引 组的结束索引+1
System.out.println(matcher.start(0) +" "+matcher.end(0));
string = matcher.group(1);
System.out.println(string);
/*
1a
1a
0 2
a
2e
2e
3 5
e
*/
}
}
匹配
public static void main(String[] args) {
Pattern pattern = Pattern.compile("\\d([a-z])");
Matcher matcher = pattern.matcher("1a,2e");
// System.out.println(matcher.group()); // 只有匹配后才可以使用
// 字符串始符合时就成功
boolean b = matcher.lookingAt();
String string = matcher.group();
System.out.println(string); // 1a
// 只有全部符合时才成功
b = matcher.matches();
System.out.println(matcher.group()); // java.lang.IllegalStateException
// 可以在任意位置匹配(其含有find(i)方法)
while(matcher.find()){
System.out.println(matcher.group());
}
}
Pattern标记
以下标记来自Pattern中的常量。
编译标记 | 效果 |
---|---|
CANON_EQ | 启用规范等价。 |
CASE_INSENSITIVE | 启用不区分大小写的匹配。 |
COMMENTS | 模式中允许空白和注释。 |
DOTALL | 启用 dotall 模式。 |
LITERAL | 启用模式的字面值解析。 |
MULTILINE | 启用多行模式。 |
UNICODE_CASE | 启用 Unicode 感知的大小写折叠。 |
UNIX_LINES | 启用UNIX行模式。 |
public static void main(String[] args) {
Pattern pattern = Pattern.compile("java",Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
Matcher matcher = pattern.matcher("java Java sJAVA");
while(matcher.find()){
System.out.println(matcher.group());
/*
java
Java
JAVA
*/
}
}
13.6.5 split()
public static void main(String[] args) {
String[] strings = "1a2b3c".split("[0-9]");
System.out.println(Arrays.toString(strings));
// 最多分出两个
strings = "1a2b3c".split("[0-9]", 2);
System.out.println(Arrays.toString(strings));
}
/*
[, a, b, c]
[, a2b3c]
*/
13.6.6 替换
public static void main(String[] args) {
String string = "1a2b3c4d+++";
System.out.println(string.replaceFirst("\\d", "*")); // *a2b3c4d
System.out.println(string.replaceAll("[a-z]", "*")); // 1*2*3*4*
Pattern pattern = Pattern.compile("\\d[a-z]");
Matcher matcher = pattern.matcher(string);
StringBuffer sb = new StringBuffer();
int i = 0;
while(matcher.find()){
// 执行渐进式替换
if (i < 2) {
matcher.appendReplacement(sb, "*");
}else {
matcher.appendReplacement(sb, "#");
}
i++;
System.out.println(sb.toString());
/*
*
**
**#
**##
*/
}
// 将字符串剩余部分添加到缓冲区
matcher.appendTail(sb);
System.out.println(sb.toString()); // **##+++
}
13.6.7 reset()
重置要匹配的字符串
public static void main(String[] args) {
Pattern pattern = Pattern.compile("\\d");
Matcher matcher = pattern.matcher("a1b1");
while(matcher.find()){
System.out.println(matcher.group());
}
matcher.reset("2c3d");
while(matcher.find()){
System.out.println(matcher.group());
}
}
/*
1
1
2
3
*/
13.7 扫描输入
使用输入流
public static void main(String[] args) throws IOException {
String s = "hello world \n24 172";
StringReader sr = new StringReader(s);
BufferedReader br = new BufferedReader(sr);
System.out.println("打招呼:");
// 按行读取数据
String temp = br.readLine();
System.out.println(temp);
System.out.println("年龄:");
temp = br.readLine();
String[] strings = temp.split(" ");
// 切割读入的数据
System.out.println(strings[0]);
System.out.println("身高:");
System.out.println(strings[1]);
}
/*
打招呼:
hello world
年龄:
24
身高:
172
*/
使用scanner
public static void main(String[] args) throws IOException {
String s = "hello world \n24 172.25";
StringReader sr = new StringReader(s);
BufferedReader br = new BufferedReader(sr);
Scanner scanner = new Scanner(br);
System.out.println("打招呼:");
String temp = scanner.nextLine();
System.out.println(temp);
System.out.println("年龄:");
int age = scanner.nextInt();
System.out.println(age);
System.out.println("身高:");
double high = scanner.nextDouble();
System.out.println(high);
}
scanner可以接收InputStream/File/String/Readable对象。
Scanner定界符
默认使用空白符,可以通过正则表达式自定义定界符。
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner("12,32 , 44");
// 空白符和,
scanner.useDelimiter("\\s*,\\s*");
while (scanner.hasNext()) {
System.out.println(scanner.next());
/*
12
32
44
*/
}
// 获得定界符
Pattern p = scanner.delimiter();
System.out.println(p); // \s*,\s*
}
正则表达式匹配
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner("12 3 e 4");
String pattern = "[0-9]+";
// 匹配下一个是否符合,到第3个时,不符合规则,就不继续了
while(scanner.hasNext(pattern)){
// 找到符合的并下移一位
scanner.next(pattern);
MatchResult m = scanner.match();
String string = m.group(0);
System.out.println(string);
/*
12
3
*/
}
}
13.8 StringTokenizer
该类是1.0时就有的,不支持正则表达式,基本被废弃。
public static void main(String[] args) {
String string = "hello,world";
StringTokenizer stringTokenizer = new StringTokenizer(string,",");
while (stringTokenizer.hasMoreElements()) {
Object object = (Object) stringTokenizer.nextElement();
System.out.println(object);
/*
hello
world
*/
}
}