字符串(编程思想)
字符串
不可变的String
String对象是不可变的。查看文档会发现,String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,已包含修改后的字符串内容。
public class Immutable {
public static String uppercase(String s) {
return s.toUpperCase();
}
public static void main(String[] args) {
String q = "howdy";
System.out.println(q);
String qq = uppercase(q);
System.out.println(qq);
System.out.println(q);
/**
当把q传递给uppercase方法时,实际传递的是引用的一个拷贝。其实,每当把String对象作为方法的参数时,都会复制一份引用,
而该引用所指的对象起始一直待在单一的物理位置上
*/
}
}
重载 "+" 与StringBuilder
String对象是不可变的。不可变形会带来你一定的效率问题。为String对象重载的"+"操作符就是一个例子。"+"操作符可以连接字符串。
public class Concatenation {
public static void main(String[] args) {
String mango = "mango";
String s = "abc" + "mango" + "def" + 47;
System.out.println(s);
}
}
这段代码底层是这样的过程:
StringBuilder.init
StringBuilder.append
StringBuilder.append
StringBuilder.append
StringBuilder.append
StringBuilder.toString
也就是说编译器自动引入了java.lang.StringBuilder类。虽然我们在源代码中并没有使用StringBuilder类,但是编译器却自作主张的使用了它,因为它更高效。
这时候会觉得可以随意使用String对象,反正编译器会自动为你优化性能。
public class WhitherStringBuilder {
public String implicit(String[] fields) {
String result = "";
for(int i = 0; i < fields.length; i++) {
result += fields[i];
}
return result;
}
public String explicit(String[] fields) {
StringBuilder result = new StringBuilder();
for(int i = 0; i < fields.length; i++){
result.append(fields[i]);
}
return result.toString();
}
}
再次javap可以发现两个方法对应的代码:
implicit:
15 StringBuilder.init
19 StringBuilder.append
25 StringBuilder.append
28 StringBuilder.toString
explicit:
4 StringBuilder.init
20 StringBuilder.append
31 StringBuilder.toString
第一个方法每一次循环就会创建一个新的StringBuilder对象,第二个方法支胜成了StringBuilder对象。显式地创建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);
}
}
无意识的递归
一个字符串后面跟这一个"+",而再后面的对象不是String,于是编译器试着将this转换成一个String。它是怎么转换呢?正是通过调用this上的toString()方法,于是就发生了递归调用。
String的方法
构造
length()
charAt()
getChars(),getBytes()
toCharArray()
equals(),equalsIgnoreCase()
compareTo()
contains()
contentEquals()
equalsIgnoreCase()
regionMatcher()
startWith()
endWith()
indexOf(),lastIndexOf()
substring()
concat()
replace()
toLowerCase(),toUpperCase()
trim()
valueOf()
intern()
格式化输出
不想看,跳过
正则表达式
java中\转义字符
正则中 括号将表达式分组,竖直线|表示或操作
String自带一个非常有用的正则表达式工具————split()方法,其功能是“将字符串从正则表达式匹配的地方切开”
public class Splitting {
private static String knights = "Then, when you have found the shrubbery, you must cut down the mightiest tree " +
"in the forest... with ... a herring!";
public static void split(String regex){
System.out.println(Arrays.toString(knights.split(regex)));
}
public static void main(String[] args) {
split(" ");
split("\\W+"); //非单词字符,\w表示的是单词字符
split("n\\W+");
}
}
split()还有一个重载的版本,它云允许你限制字符串分隔的次数。
String类自带的最后一个正则表达式工具是“替换”。你可以只替换正则表达式第一个匹配的子串,或者是替换所有匹配的地方
public class Replacing {
static String s = Splitting.knights;
public static void main(String[] args) {
System.out.println(s.replaceFirst("f\\w+", "located"));
System.out.println(s.replaceAll("shrubbery|tree|herring", "banana"));
}
}
创建正则表达式
字典:
字符类:
. 任意字符
[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字符(与a|b|c|h|i|j作用相同)(合并)
[a-z&&[hij]] 任何h、i或者j(交集)
\s 空包符
\S 非空白符([^\s])
\d 数字[0-9]
\D 非数字
\w 词字符[a-zA-Z0-9]
\W 非词字符[^\w]
逻辑操作符
XY Y跟在X后面
X|Y X或Y
(X) 捕获组(capturing group)。可以在表达式中庸\i引出第i个捕获组
边界匹配符
^ 一行的开始
$ 一行的结束
\b 词的边界
\B 非词的边界
\G 前一个匹配的结束
public class Rudolph {
public static void main(String[] args) {
for(String pattern : new String[]{ "Rudolph", "[rR]udolph", "[rR][zeiou][a-z]ol.*", "R.*"}){
System.out.println("Rudolph".matches(pattern));
}
}
}
量词:
贪婪型:
量词总是贪婪的,除非有其他的选项被设置。贪婪表达式会为所有可能的模式发现尽可能多的匹配。导致此问题的一个典型理由就是假定我们的模式仅能匹配第一个可能的字符组,如果它是贪婪的,那么它就会继续往下匹配。
勉强型:
用问好来指定,这个量词满足匹配满足模式所需要的最少字符数。因此也被称作懒惰的、最少匹配的、非贪婪的、或不贪婪的。
贪婪型 勉强型 如何匹配
X? X?? 一个或零个X
X* X*? 零个或多个X
X+ X+? 一个或多个X
X{n} X{n}? 恰好n次X
X{n,} X{n,}? 至少n次X
X{n,m} X{n,m}? X至少n次,且不超过m次。
Pattern和Matcher
一般来说,我们一般更愿意构造功能强大的正则表达式对象。导入java,util.regex包,使用static Pattern.compile()方法编译你的正则表达式即可。它会根据你的String类型的正则表达式生成一个Pattern对象。接下来,将你想要检索的字符串传入Pattern对象的matcher()方法。matcher()方法会生成一个Matcher对象,它有很多功能可用。
Pattern.matcher()方法,传入字符串参数,得到Matcher对象
find()
Matcher.find()方法可用来在CharSequence中查找多个匹配
find()像迭代器那样向前遍历输入字符串。而第二个find()能够接受一个整数作为参数,该整数表示字符串中字符的位置,并作为其搜索的起点
public class Finding {
public static void main(String[] args) {
Matcher m = Pattern.compile("\\w+")
.matcher("Evening is full of the linnet's wings");
while(m.find()){
System.out.println(m.group() + " ");
}
System.out.println();
int i = 0;
while(m.find(i)){
System.out.println(m.group() + " ");
i++;
}
}
}
组(Groups)
组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为0表示整个表达式,组号为1表示被第一对括号括起来的组,以此类推。
A(B(C))D 组0:ABCD 组1:BC 组2:C
Matcher对象提供了一系列方法,用以获取与组有关的信息:
groupCount()返回该匹配器中的模式中的分组数目,第0组不包括在内
group()返回前一次匹配操作的第0组
group(int i)返回在前一次匹配操作期间指定的组好,如果匹配成功,但指定的组没有匹配输入字符串的任何部分,将会返回null
start(int group)返回在前一次匹配中寻找到的组的起始索引
end(int group)返回在前提一次匹配操作中寻找到的最后一个字符串索引加一的值。
public class Groups {
static public final String POEM =
"Twas brilling, abd in the slithy toves\nDid gyre and gimble in the wabe.\nAll mimsy were the borogoves,\n" +
"And the mome raths outgrabe.\n\nBeware the Jabberwock, my son,\nThe jaws thar bite, the claws that catch.\n" +
"Beware the Jubjub bird, and shun\nThe frumious Bandersnatch.";
public static void main(String[] args) {
Matcher m = Pattern.compile("(?m)(\\S+)\\s+((\\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();
}
}
}
Pattern标记
Pattern类的compile()方法还有另一个版本,它接受一个标记参数,以调整匹配的行为:
Pattern pattern.compile(String regex, int flag)
其中的flag来自以下Pattern类中的常量:
编译标记 效果
Pattern.CANON_EQ 两个字符当且仅当他们的完全规范分解相匹配时,就认为它们是匹配的。
Pattern.CASE_INSENSITIVE(?i) 这个标记允许模式匹配不必考虑大小写
Pattern.COMMENTS(?x) 这个模式下,空格符将被忽略掉,并且以#开始直到行末的注释也会被忽略掉
Pattern.DOTALL(?s) 在此模式下,表达式"."匹配所有字符,包括行终止符。默认情况下,"."不匹配行终止符
Pattern.MULTILINE(?m) 在多行模式下,表达式^和$分别匹配一行的开始和结束。^还匹配输入字符串的开始,而$还匹配输入字符串的结尾。默认情况下,这些表达式仅匹配输入的完整字符串的开始和结束
Pattern.UNICODE_CASE(?u) 当指定这个标记,并且开启CASE_INSENSITIVE时,大小写不敏感的匹配将按照与Unicode标准相一直的方法进行。默认情况下,大小写不敏感的假定只能在US-ASCII字符集中的字符才能进行
Pattern.UNIX_LINES(?d) 在这种模式下,在.、^和$行为中,只识别行终止符\n
你还可以通过“或”(|)操作符组合多个标记的功能
public class RefFlags {
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 pretty good regular expression\n" +
"Regular expressions are in Java"
);
while(m.find()){
System.out.println(m.group());
}
}
}
split()
split()将输入字符串断开成字符串对象数组,断开便捷由下列正则表达式确定:
String[] split(CharSequence input)
String[] split(CharSequence input, int limit)
替换操作
正则表达式特别便于替换文本,它提供了许多方法:
replaceFirst(String replacement)以参数字符串replacement替换掉第一个成功匹配的部分
replaceAll(String replacement) 一参数字符串replacement替换所有匹配成功的部分
appendReplacement(StringBuffer sbuf, String replacement)执行渐进式的替换。它允许你调用其他方法来生成或处理replacement,使你能以变成的方式将目标分隔成组,从而具备更强大的替换功能
appendTail(StringBuffer sbuf)在执行了一次货多次appendReplacement()之后,调用此方法可以将输入字符串余下的部分复制到sbuf中
public class TheReplacements {
public static void main(String[] args) {
Pattern p = Pattern.compile("cat");
Matcher m = p.matcher("one cat two cats in the yard");
StringBuffer sb = new StringBuffer();
StringBuffer sb1 = new StringBuffer();
m.find();
m.appendReplacement(sb, "dog");
m.find();
m.appendReplacement(sb1, "pigs");
// while (m.find()) {
// m.appendReplacement(sb, "dog");
// }
// m.appendTail(sb);
m.appendTail(sb);
System.out.println(sb.toString());
System.out.println(sb1.toString());
/**
* 简单的讲讲,用matcher.appendReplacement(?sb, "b")就是在Matcher.find()找到匹配的地方用“b”替换掉然后加进StringBuffer中去,
* 这时后面可能还有字符串但是不匹配的,就像例子中@163.com是匹配到最后一个a结束的地方如果这时输出就是bbcbbcb。
* ?matcher.appendTail()就是把@163.com加上而已
*
* 我的理解:
* matcher.appendReplacement()就是从指针开始处~匹配出的字符串中匹配部分替换字符为"b",然后append进sb
* matcher.appendTail()就是将指针~末尾剩下的部分append到sb中
*/
}
}
reset()
通过reset()方法,可以将现有的Matcher对象应用于一个新的字符序列:
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.print(m.group() + " ");
}
System.out.println();
m.reset("fix the rig with rags");
while(m.find()){
System.out.print(m.group() + " ");
}
}
}
使用不带参数的reset()方法,可以将Mather对象重新设置到当前字符序列的起始位置
扫描输入
通过读取文件:
public class SimpleRead {
public static BufferedReader input = new BufferedReader(new StringReader("Sir Robin of Camelot\n22 1.61803"));
public static void main(String[] args) {
try{
System.out.println("What is your name?");
String name = input.readLine();
System.out.println(name);
System.out.println("How old are you? What is your favorite double?");
System.out.println("(input: <age> <double>)");
String numbers = input.readLine();
System.out.println(numbers);
String[] numArray = numbers.split(" ");
int age = Integer.parseInt(numArray[0]);
double favorite = Double.parseDouble(numArray[1]);
System.out.println("Hi, " + name);
System.out.println("In 5 years you will be" + (age + 5));
System.out.println("My favorite double is " + favorite / 2);
}catch(IOException ex){
System.out.println("I/O exception");
}
}
}
使用Scanner类读取:
public class SimpleRead {
public static BufferedReader input = new BufferedReader(new StringReader("Sir Robin of Camelot\n22 1.61803"));
public static void main(String[] args) {
try{
System.out.println("What is your name?");
String name = input.readLine();
System.out.println(name);
System.out.println("How old are you? What is your favorite double?");
System.out.println("(input: <age> <double>)");
String numbers = input.readLine();
System.out.println(numbers);
String[] numArray = numbers.split(" ");
int age = Integer.parseInt(numArray[0]);
double favorite = Double.parseDouble(numArray[1]);
System.out.println("Hi, " + name);
System.out.println("In 5 years you will be" + (age + 5));
System.out.println("My favorite double is " + favorite / 2);
}catch(IOException ex){
System.out.println("I/O exception");
}
}
}