个人吐槽:对于这章,第一遍读和重读之后果然看法不同:第一次读时觉得,文章用了大篇幅来介绍 API,这样我可以比较容易理解这个方法是干嘛的,以此理解这个类是干嘛的;但是重读之后觉得,文章居然用了这么大篇幅在讲 API(尤其是正则表达式),如果我需要知道具体某个方法怎么用,直接去官方文档查多好啊,这种情况下,我更希望得到某个方法可能出现的坑,而不只是方法的介绍(毕竟,这往往需要踩过坑之后总结的,hhh)。
1. String 基础
1.1 String 不可变
String 对象是不可变的。(只读性)
String 类中每一个修改 String 值的方法,实际上都是创建了一个新的 String 对象,原有的 String 对象丝毫未动。
1.2 String 的连接问题
"+" 和 "+=" 是经过 Java 重载之后的两个操作符,可用来对 String 拼接。
同时 Java 不允许程序员重载任何操作符。
String + String 与 StringBuilder.append() 对比
直接相加字符串(不含 String 引用,形如"hello"+"world"):效率最高,简单来说,其全部为常量,编译期就会优化为 "helloworld",编译期值就已经确定了。
间接相加字符串(含 String 引用,如 str + "world"):效率最差,简单来说会取出原有 String 与 "world" 进行拼接,然后将结果存入另一个新的 String 对象中,虽然编译器进行了优化,通过 StringBuilder.append() 来实现拼接,但是当出现循环时,会出现 StringBuilder 对象的反复生成。
-
StringBuilder.append():介于上述二者之间。
综上:
- 如果是固定字符串如“xxx”,那么直接用 "+" 即可。
- 如果间接相加字符串,但是次数很少,则 StringBuilder 和 "+" 都可,差别不很大
- 如果简介相加字符串,且多次循环,那么强烈建议 StringBuilder
1.3 无意识的递归
当想要打印出某对象的内存地址时,应该调用 Object.toString() / super.toString() 方法,而非 this 上的 toString()。
换句话说,就是当前对象重写的 toString() 方法中,应该调用 super.toString() 而不是使用 this。
在当前对象的 toString() 方法中,如果调用 this,会发生自动类型转换,将对象类型转换为 String 类型。那么怎么转换呢?正式通过调用 this (当前对象)上的 toString() 方法。
em...完美的形成一个递归,自己调用自己,且没有终止条件。
1.4 String API
这里有想知道的一切 API
https://docs.oracle.com/javase/9/docs/api/java/lang/String.html
1.5 格式化输出
如下几种方式:
System.out.println() 强行凑成指定形式,不推荐
System.out.format():针对于 PrintStream 和 PrintWriter,其中包括 System.out 对象
-
Formatter 类:可以看作是一个翻译器,将格式化字符串与数据翻译成需要的结果。
-
格式化说明符:%[argument_index$][flags][width][.precision]conversion
width 指定域的最小尺寸
precision 指定最大尺寸,适用于 String 和浮点数,无法用于整数
-
常用的类型转换
类型转换字符 意义 类型转换字符 意义 d 十进制整数型 e 科学计数浮点数 c Unicode 字符 x 十六进制整数 b Boolean 值 h 十六进制散列码 s String % 字符 "%" f 十进制浮点数 更多详见 https://docs.oracle.com/javase/9/docs/api/java/util/Formatter.html
-
-
String.format()
- 实际上就是内部封装了 Formatter 对象,然后将传入的参数传递给 Formatter,由它来进行具体的处理。
2. 正则表达式
2.1 通用正则表达式
就不整理出来了,放上一篇教程
http://www.runoob.com/regexp/regexp-tutorial.html
https://docs.oracle.com/javase/9/docs/api/java/util/regex/Pattern.html
补充一点:
- 在 java 中,"\\" 表示一个有效的 "\",因此如果需要表示正则表达式里的一个数字 "\d",需要写成 "\\d"
2.2 Pattern & Matcher
接口 CharSequence 从 CharBuffer、String、String Buffer、StringBuilder 类之中抽象出了字符序列的一般化定义:
interface CharSequence { charAt(int i); length(); subSequence(int start,int end); toString(); }
本小节简单介绍 Java 中如何处理正则表达式:
2.2.1 基础使用:
step 1:Pattern.compile(regex) 编译 String 类型的 regex,并产生 Pattern 对象
step 2:Pattern.matcher(待检索的字符串)生成一个 Matcher 对象
直接上例子,通过例子说明:
import java.util.regex;
public class TestRegularExpression {
public static void main(String[] args) {
String[] array = {"aabbcc", "aab", "aab+", "(b+)"};
for (String arg : array) {
System.out.println();
print("Regular expression: \"" + arg + "\"");
Pattern p = Pattern.compile(arg); // step1: Pattern 表示编译后的匹配模型Pattern.(编译后的正则表达式)
Matcher m = p.matcher("aabbcc"); // step2: 模型实例 检索 待匹配字符串并 生成一个匹配对象Matcher, Matcher有很多方法
while (m.find()) {
print("Match \"" + m.group() // 待匹配的字符串
+ "\" at positions "
+ m.start() // 字符串匹配regex的起始位置
+ "-" + (m.end() - 1)); // 字符串匹配regex的终点位置
}
}
}
Pattern 对象表示编译后的正则表达式,重点在于 Matcher 对象,它提供了一系列方法来进行正则的匹配,下面简单介绍:
详见 https://docs.oracle.com/javase/9/docs/api/overview-summary.html
Matcher.find():用来在 CharSequence 查找多个匹配
-
Matcher.group():用来获取与组相关的信息
组是用括号划分的正则表达式,可以用组的编号来引用某个组,组0表示整个表达式,组1表示被第一队括号括起来的组。。。
A(B(C))D : 组0是 ABCD,组1是B,组2是C
Matcher.start() & end():返回先前匹配的起始和截止位置的索引。
-
Pattern 标记:重载方法,Pattern.compile(String regex, int flags) 接受一个 flags 参数,来调整匹配的行为。具体看官方文档:
https://docs.oracle.com/javase/9/docs/api/java/util/regex/Pattern.html
split():将输入字符串断开成字符串对象数组。
替换 replacexxx():替换文本
reset():将现有的 Matcher 对象应用于一个新的字符序列。
3. 扫描输入
Scanner 是 Java SE5 中添加的特性,主要就是减轻扫描输入的工作负担,最实用的是获取控制台输入,其他比如从文件读取内容的,emmm...感觉有些鸡肋。
举个使用的简单例子:
public class SimpleScanner {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.println("请输入字符串:");
while (true) {
String line = s.nextLine();
if (line.equals("exit")) break;
System.out.println(">>>" + line);
}
}
}
/*
请输入字符串:
whdalive
>>>whdalive
exit
Process finished with exit code 0
*/
Scanner 使用很方便,这是因为它的构造器可以接受任何类型的输入对象,有了 Scanner 之后,所有输入、分词以及翻译的操作都隐藏在不同类型的 next 方法中。
3.1 Scanner 定界符
默认情况下,Scanner 根据空白字符对输入进行分词,但是我们可以用正则表达式指定自己所需的定界符。
public static void main(String[] args) throws FileNotFoundException {
Scanner s = new Scanner("12,42,78,99,42");
s.useDelimiter(",");
while (s.hasNext()) {
System.out.println(s.next());
}
}
/*输出
12
42
78
99
42
*/
我们通过 useDelimiter() 方法来指定定界符,显然上述代码中我们使用的是逗号。
3.2 正则表达式扫描
Scanner 的 next 方法中,有一个重载方法可以接收 String 的正则表达式,此时它会找到下一个匹配该模式的输入部分,然后调用 match() 方法就可以获得匹配的结果。工作方式和正则表达式匹配是类似的。
但是需要注意一点:它仅仅针对下一个输入分词进行匹配,如果正则表达式中有定界符,那么永远不可能匹配成功。