13 字符串
13.1 不可以变的 String
string 对象是不可变的。String 类中每一个看起来会修改 String 值的方法,其实都是创造了一个全新的 String 对象。其实这正是我们想要的,对一个方法而言,参数只是提供信息,而不是想让方法改变自己的。
13.2 重载 “+” 与 StringBuilder
String 对象是不可变的,你可以给 String 对象加任意多的别名。因为 String对象具有只读特性,所以任何指向它的引用都不可能改变它的值,因此,也就不会对其他应用产生什么影响。
不可变性会带来效率问题。为 String 对象重载的 “+” 操作符就是一个例子。重载的意思是,一个操作符在应用于特定的类时,被赋予了特殊的意义(用于 String 的 + 和 += 是Java中仅有的两个重载操作符,而Java并不允许程序员重载任何操作符。)
操作符 “+” 可以用来连接 String:
String s = "a" + "b" + "c"
这样简单的语句,编译器会做出优化。但是如果在方法中使用循环,应该使用StringBuilder.
public class UsingStringBuilder {
public static Random rand = new Random(47);
public String toString(){
StringBuilder result = new StringBuilder("[");
for(int i = 0;i < 25; i++){
result.append(rand.nextInt(100));
result.append(". ");
}
result.delete(result.length() -2, result.length());
result.append("]");
return result.toString();
}
public static void main(String[] args){
UsingStringBuilder usb = new UsingStringBuilder();
System.out.println(usb);
}
}
StringBuilder 提供了非常丰富的方法,包括 insert() / replace() / subString() / reverse(), 但是最常用的还是 append() 和 toString(), 还有 delete() 方法。
StringBuilder 是 java5 引进的,在此之前使用的是 StringBuffer, StringBuffer 是线程安全的,因此开销会有些大。
13.3 无意识的递归
java 中的每个类从根本上都是继承自 Object,标准容器也是如此,因此容器类都有 toString() 方法。并且覆盖了该方法,使得它生成的 string 结果能表达容器自身,以及容器所包含的对象。
13.4 String 的操作
方法 | 参数,重载版本 | 应用 |
---|---|---|
length | String 中字符串的个数 | |
charAt | Int 索引 | 取得String上该索引位置上的 char |
getChars() getBytes() | 要复制部分的起点和终点的索引,复制目标数组,目标数组的起始索引 | 复制 char 或 byte 到一个目标数组的中 |
toCharArray() | 生成一个char[],包含String 中所有的字符 | |
equals, equalsIgnoreCase() | 与之进行比较的String | 比较两个String 的内容是否相同 |
compareTo | 与之比较的String | 按词典的位置比较String的内容,比较结果为负数、零或正数。大小写不等价 |
contains | 要搜索的CharSequence | 返回bool |
contentEquals | 与之比较的charSequence或StringBuffer | 如果该String与参数完全一直,则返回true |
equalsIgnoreCase | 与之进行比较的String | 忽略大小写,比较内容 |
regionMatcher | 该String的索引偏移量,另一个String及其索引偏移量 | 返回boolean结果 |
startsWith | 可能的起始String | 返回 boolean结果 |
endsWith | 该String可能的后缀String | 返回 boolean 结果 |
indexOf, lastIndexOf | 重载版本包括:char, char 与 起始索引,String,String与起始索引 | 如果不包含此参数则返回-1,lastIndexOf 是从后向前索引 |
substring() | 起始索引 | 截取字符串 |
concat | join字符串 | |
replace | 替换字符串 | |
toLowerCase / toUpperCase | 字符串大小写转化 | |
trim | 将string 两端的空白符删除后,返回一个新的字符串 | |
valueOf | 返回一个表示参数内容的String | |
intern | 为每个唯一的字符序列生成一个String引用 |
13.6 正则表达式
反斜线:
java 中 \ 表示正则表达式中的反斜线。如python 中的 \d 要写为 \d
\\ 表示转义,表示反斜线本身。
将数组打印出来
一般来说,Java 原生的 API 一般转化为装箱类型,然后用包装类型的类型 toString 方法来输出
// 数组转化为 Array 输出
String[] s = {"a", "b", "c"}
System.out.println(Arrays.toString(s ))
String 中使用正则表达式的API,String.split() / String.replace()
13.6.2 创建正则表达式
正则表达式字符类
- | 字符类 |
---|---|
. |
任意字符 |
[abc] |
包含a b c的任何字符和a/b/c 相同 |
[^abc] |
除a b c之外的任何字符 |
[a-zA-Z] |
从A-Z 或 从a-z 的任何字符 |
[abc[hij]] |
a / b / c / h/i/j 的作用相同,也就是匹配其中一个字符。python js 不支持此语法 |
a-z&&[hij] |
任何hij中任意1个,python js 不支持此表达 |
\s |
空白符,空格、tab / 换行 、换页、回车 |
\S |
[^\s] 非空白符 |
\d |
数字[0-9] |
\D |
非数字,[^0-9] |
\w |
词字符(word),[a-zA-Z0-9] |
\W |
非词字符,[^\w] |
逻辑操作符号
逻辑操作符 | ||
---|---|---|
xy | y跟在x后面 | |
x | y | x或y |
(x) | 捕获组,可以在表达式中用\i引用第i个捕获组 |
边界匹配符号 | |
---|---|
^ |
一行的起始 |
$ |
一行的结束 |
\b |
词的边界 |
\B |
非词的边界 |
\G |
前一个匹配的结束 |
边界匹配符号
边界匹配符号 | |
---|---|
^ |
一行的起始 |
$ |
一行的结束 |
\b |
词的边界 |
\B |
非词的边界 |
\G |
前一个匹配的结束 |
13.6.3 量词
量词描述了一个模式吸收输入文本的方式。
- 贪婪型:量词总是贪婪的,除非有其他选项被设置。
- 勉强型:这个量词匹配满足模式所需的最少字符数
- 占有型:仅在Java 中可用。用于防止表达式失控
贪婪型 | 勉强型 | 占有型 | 如何匹配 |
---|---|---|---|
x? |
x?? |
x?+ |
一个或零个x |
x* |
x*? |
x*+ |
零个或多个x |
x+ |
x+? |
x++ |
1个或多个x |
x{n} |
x{n}? |
x{n}+ |
恰好n个x |
x{n,} |
x{n,}? |
x{n}+ |
至少n个x |
x{n,m} |
x{n,m}? |
x{n,m}+ |
n,m个x |
表示式x通常必须要用圆括号括起来,以便它能按照我们期望的方式工作,比如 (abc)+
接口 CharSequence 从 CharBuffer / String / StringBuffer / StringBuilder 之中抽象出了字符串序列的一般化定义:
interface CharSequence{
charAt(int i);
length();
subSequence(int start, int end);
toString();
}
这些类都实现了该接口.多数正则表达式接受 CharSequence 作为参数
13.6.4 Pattern 和 Matcher
导入 regex 包,使用 static Pattern.compile 方法编译你的正则表达式. 这个方法会生成一个 Pattern 对象.这点和 Python 一样.
接下来,可以把想检索的字符串给 Pattern 的 matcher 方法.matcher 方法会生成一个 Matcher 对象,它有很多功能可用.
public class TestRegularExpression {
static String s = "abcabcabcdefabc";
public static void main(String[] args){
print("abc+");
print("(abc)+");
print("(abc){2,}");
}
private static void print(String p){
System.out.println(p);
Pattern pattern = Pattern.compile(p);
Matcher matcher = pattern.matcher(s);
while (matcher.find()){
System.out.println("Match \""+ matcher.group() +"\" at positions " + matcher.start() + "-" + (matcher.end()-1));
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>");
}
}
组(groups)
组是用括号划分的正则表达式,可以依据组的编号来引用某个组.组号为0 表示整个表达式,组号为1表示第一对括号括起来的正则表达式,以此类推
A(B(C))D
这个表达式中有三个组, 组0 是 ABCD, 组1 是 BC, 组2 是C
Matcher 对象提供了一系列方法,用以获取和组相关的信息:
-
public int groupCount
,返回该匹配器模式中的分组数目,第0组不包括在内 -
public String group
返回前一次匹配操作(例如find)的第0组 -
public String group(i)
返回前一次匹配操作的第i组,如果匹配成功,但是指定的组没有匹配输入的字符串的任何部分,返回null -
public int start(int group)
返回前一次匹配操作中寻找到的组的起始索引 -
public int end(int group)
返回前一次匹配操作中寻找到组的最后一个字符索引加一的值
public class Groups {
static public final String POEM = "I came as tomorrow\n"+
"\n"+
"Swaddled in innocence\n"+
"\n"+
"To your warm womb\n"+
"\n"+
"Mother……\n"+
"\n"+
"Without your choice\n"+
"\n"+
"Or mine\n"+
"\n"+
"Destined to up date\n"+
"\n"+
"With time";
public static void main(String[] args){
Matcher m = Pattern.compile("(?m)(\\S+)\\s+(\\S+)$").matcher(POEM);
while (m.find()){
for(int j = 0; j < m.groupCount();j++){
System.out.print("[" + m.group(j) + "]");
}
System.out.println();
}
}
}
send() 与 end()
在匹配操作成功后,start() 返回先前匹配的起始位置的索引,而 end() 返回所匹配的最后字符的索引加一的值。
Pattern 标记
编译标记 | 效果 |
---|---|
Pattern.CANON_EQ | 两个字符当且仅当它们的完全规范分解相匹配时,就确认它们是匹配的。例如,如果我们指定这个标记,表达式 a\u030A 就会匹配字符串。 |
Pattern.CASE_INSENSITIVE(?!) | 忽略大小写 |
Pattern.COMMENTS(?x) | 空格符将被忽略,# 从开始到行尾的注释也忽略掉 |
Pattern.DOTALL(?s) | 默认情况下,“.”表达式不匹配行终结符;此模式下,. 匹配一切符号 |
Pattern.MULTILINE(?m) | 在多行模式下,表达式 ^ 和 $ 分别匹配一行的开始和结束 |
Pattern.UNICODE_CASE(?u) | 看不懂 |
public class ReFlags {
public static void main(String[] args){
//多行,大小写不敏感
Pattern p = Pattern.compile("^java", Pattern.CASE_INSENSITIVE| Pattern.MULTILINE);
Matcher m = p.matcher("java has regex\njava has regex\n" +
"java has regex java has regex\n" +
"JAVA has regex");
while (m.find()){
System.out.println(m.find());
System.out.println(m.group());
}
}
}
13.6.5 split
split() 方法将输入字符串断开成字符串对象数组,断开边界由下列正则表达式确定:
String[] split(CharSequence input)
String[] split(CharSequence input, int limit)
case
public class SplitDemo {
public static void main(String[] args){
String input = "This is!This is!This is!This is!This is!This is!";
System.out.println(Arrays.toString(Pattern.compile("!").split(input)));
System.out.println(Arrays.toString(Pattern.compile("!").split(input, 3)));
}
}
1.3.6.6 替换操作
-
replaceFirst(String replacement)
以参数字符串 replacement 替换第一个匹配成功的部分 -
replaceAll(String replacement)
以参数字符串 replacement 替换掉所有匹配成功的部分 -
appendReplacement(StringBuffer sbuf, String replacement)
, 渐进式替换。它允许你调用其他方法来生成或处理 replacement,以编程的方式分割成组,从而有更强大的替换能力。 -
appendTail(StringBuffer sbuf)
, 在执行一次或多次 appendReplacement() 之后,调用此方法可以将输入字符串余下的部分复制到 sbuf 中。
13.6.7 reset()
reset , 去匹配新的字符串
public class Resetting {
public static void main(String[] args) throws Exception{
Matcher m = Pattern.compile("[frb][aiu][gx]").matcher("fix the rug with bags");
while (m.find()){
System.out.println(m.group() + " ");
}
m.reset("fag fag fag fag");
while (m.find()){
System.out.println(m.group() + " ");
}
}
}