《Java编程思想》第13章 字符串

https://github.com/yangxian1229/ThinkingInJava_SourceCode

13.1 不可变String

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

13.2 重载“+”与StringBuilder

用于String的“+”与“+=”是Java中仅有的两个重载过的操作符,而java并不允许程序员重载任何操作符。
当你为一个类编写toString()方法时,如果字符串操作比较简单,那就可以信赖编译器,它会为你合理地构造最终的字符串结果。但是,如果你要在toString()方法中使用循环,那么最好自己创建一个StringBuilder对象,用它来构造最终的结果。

//: strings/UsingStringBuilder.java
import java.util.*;

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);
  }
} /* Output:
[58, 55, 93, 61, 61, 29, 68, 0, 22, 7, 88, 28, 51, 89, 9, 78, 98, 61, 20, 58, 16, 40, 11, 22, 4]
*///:~

StringBuilder提供了丰富而全面的方法,包括insert()、repleace()、substring()甚至reverse(),但是最常用的还是append()和toString()。还有delete()方法。

13.3 无意识的递归

13.4 String上的操作

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

13.5 格式化输出

13.5.1 printf()

13.5.2 System.out.format()

//: strings/SimpleFormat.java

public class SimpleFormat {
  public static void main(String[] args) {
    int x = 5;
    double y = 5.332542;
    // The old way:
    System.out.println("Row 1: [" + x + " " + y + "]");
    // The new way:
    System.out.format("Row 1: [%d %f]\n", x, y);
    // or
    System.out.printf("Row 1: [%d %f]\n", x, y);
  }
} /* Output:
Row 1: [5 5.332542]
Row 1: [5 5.332542]
Row 1: [5 5.332542]
*///:~

format()与printf()是等价的。

13.5.3 Formatter类

当你创建一个Formatter对象的时候,需要向其构造器传递一些信息,告诉它最终将向哪里输出。

13.5.4 格式化说明符

%[argument_index$][flags][width][.precision]conversion
下面表格包含了最常用的类型转换:
在这里插入图片描述

13.5.6 String.format()

String.format()是一个static方法,它接受与Formatter.format()方法一样的参数,但返回一个String对象。

13.6 正则表达式

正则表达式是一种强大而灵活的文本处理工具,能够解决各种字符串处理相关的问题:匹配、选择、编辑以及验证。

13.6.1 基础

Java对反斜线\的不同处理:\的意思是“我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。”例如,如果你想表示一位数字,那么正则表达式应该是\d。如果你想插入一个普通的反斜线,则应该这样\\。不过换行和制表符之类的东西只需使用单反斜线:\n\t。
String类还自带了一个非常有用的正则表达式工具——split()方法,其功能是“将字符串从正则表达式匹配的地方切开。”

//: strings/Splitting.java
import java.util.*;

public class Splitting {
  public 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(" "); // Doesn't have to contain regex chars
    split("\\W+"); // Non-word characters
    split("n\\W+"); // 'n' followed by non-word characters
  }
} /* Output:
[Then,, when, you, have, found, the, shrubbery,, you, must, cut, down, the, mightiest, tree, in, the, forest..., with..., a, herring!]
[Then, when, you, have, found, the, shrubbery, you, must, cut, down, the, mightiest, tree, in, the, forest, with, a, herring]
[The, whe, you have found the shrubbery, you must cut dow, the mightiest tree i, the forest... with... a herring!]
*///:~

第一个split()只是按空格划分字符串。第二个将标点符删除了。第三个split()表示“字母n后面跟着一个或多个非单词字符。”可以看到,在原始字符串中,与正则表达式匹配的部分,在最终结果中都不存在了。
String.split()还有一个重载的版本,它允许你限制字符串分割的次数。
String类自带的最后一个正则表达式工具是“替换”。你可以只替换正则表达式第一个匹配的子串,或者替换所有匹配的地方。

    print(s.replaceFirst("f\\w+", "located"));
    print(s.replaceAll("shrubbery|tree|herring","banana"));

13.6.2 创建正则表达式

在这里插入图片描述当你学会了使用字符类(character classes)之后,正则表达式的威力才能真正显现出来。以下是一些创建字符类的典型方式,以及一些预定义的类:
在这里插入图片描述在这里插入图片描述

13.6.3 量词

量词描述了一个模式吸收输入文本的方式:

  • 贪婪型:量词总是贪婪的,除非有其他的选项被设置。贪婪表达式会维所有可能的模式发现尽可能多的匹配。导致此问题的一个典型理由就是假定我们的模式技能匹配第一个可能的字符组,如果它是贪婪的,那么它就会继续往下匹配。
  • 勉强型:用问号来制定,这个量词匹配满足模式所需的最少字符数。因此也称作懒惰的、最少匹配的、非贪婪的。
  • 占有型:目前这种类型的量词只有在Java语言中才可用(在其他语言中不可用)。当正则表达式被应用于字符串时,它会产生相当多的状态,以便在匹配失败时可以回溯。而“占有的”量词并不保存这些中间状态,因此它们可以防止回溯。它们常常用于防止正则表达式失控,因此可以使正则表达式执行起来更有效。
    在这里插入图片描述应该非常清楚地意识到,表达式X通常必须要用圆括号括起来,以便它能够按照我们期望的效果去执行。

CharSequence

接口CharSequence从CharBuffer、String、StringBuffer、StringBuilder类之中抽象出了字符序列的一般化定义

interface CharSequence {
	charAt(int i);
	length();
	subSequence(int start, int end);
	toString();
}

因此,这些类都实现了该接口。多数正则表达式操作都接受CharSequece类型的参数。

13.6.4 Pattern和Matcher

一般来说,比起功能有限的String类,我们更愿意构造功能强大的正则表达式对象。只需导入java.util.regex包,然后用static Pattern.compile()方法来编译正则表达式即可。它会根据你的String类型的正则表达式生成一个Pattern对象。接下来,把你想要检索的字符串传入Pattern对象的matcher方法。matcher()方法会生成一个Matcher对象,它有很多功能可用。

String regex = "abc+";
String s = "abcabcabcabf";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(s);

Pattern对象表示编译后的正则表达式。用已编译的Pattern对象上的matcher()方法,加上一个输入字符串,从而共同构造了一个Matcher对象。同时,Pattern类还提供了static方法:
static boolean matches(String regex,CharSequence input)
该方法用以检查regex是否匹配整个CharSequence类型的input参数。
通过调用Pattern.matcher()方法,并传入一个字符串参数,我们得到一个Matcher对象。使用Matcher上的方法,我们将能够判断各种不同类型的匹配是否成功:

boolean matches()
boolean lookingAt()
boolean find()
boolean find(int start)

其中的matches()方法用来判断整个字符串是否匹配正则表达式模式,而lookingAt()则用来判断该字符串(不必是整个字符串)的始部分是否能够匹配模式。
Matcher.find()方法可用来在CharSequence中查找多个匹配。

组(Groups)

组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为0表示整个表达式,组号1表示被第一对括号括起的组,以此类推。如:
A(B©)D
中有三个组:组0是ABCD,组1是BC,组2是C。

start()与end()

在匹配操作成功之后,start()返回先前匹配的起始位置的索引,而end()返回所匹配的最后字符的索引加一的值。匹配操作失败之后调用start()或end()将会产生IllegalStateException。

Pattern标记

Pattern类的compile()方法还有另一个版本,它接受一个标记参数,以调整匹配的行为:
Pattern Pattern.compile(String regex, int flag)
其中的flag来自以下Pattern类中的常量:
在这里插入图片描述在这里插入图片描述### 13.6.5 split()
split()方法将输入字符串断开成字符串对象数组,断开边界由下列正则表达式确定:
String[] split(CharSequence input) String[] split(CharSequence input, int limit)

13.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()方法,可以将现有的Matcher对象应用于一个新的字符序列:

Matcher m = Pattern.compile("[frb][aiu][gx]").matcher("fix the rug with bags");
m.reset("fix the rug with rags");

使用不带参数的reset()方法,可以将Matcher对象重新设置到当前字符序列的起始位置。

13.6.8 正则表达式与Java I/O

下面的例子将演示,如何应用正则表达式在一个文件中进行搜索匹配操作。

// A very simple version of the "grep" program.
// {Args: JGrep.java "\\b[Ssct]\\w+"}
public class JGrep {
  public static void main(String[] args) throws Exception {
    if(args.length < 2) {
      System.out.println("Usage: java JGrep file regex");
      System.exit(0);
    }
    Pattern p = Pattern.compile(args[1]);
    // Iterate through the lines of the input file:
    int index = 0;
    Matcher m = p.matcher("");
    for(String line : new TextFile(args[0])) {
      m.reset(line);
      while(m.find())
        System.out.println(index++ + ": " +
          m.group() + ": " + m.start());
    }
  }
} 

13.7 扫描输入

Scanner

//: strings/BetterRead.java
import java.util.*;

public class BetterRead {
  public static void main(String[] args) {
    Scanner stdin = new Scanner(SimpleRead.input);
    System.out.println("What is your name?");
    String name = stdin.nextLine();
    System.out.println(name);
    System.out.println(
      "How old are you? What is your favorite double?");
    System.out.println("(input: <age> <double>)");
    int age = stdin.nextInt();
    double favorite = stdin.nextDouble();
    System.out.println(age);
    System.out.println(favorite);
    System.out.format("Hi %s.\n", name);
    System.out.format("In 5 years you will be %d.\n",
      age + 5);
    System.out.format("My favorite double is %f.",
      favorite / 2);
  }
} /* Output:
What is your name?
Sir Robin of Camelot
How old are you? What is your favorite double?
(input: <age> <double>)
22
1.61803
Hi Sir Robin of Camelot.
In 5 years you will be 27.
My favorite double is 0.809015.
*///:~

Scanner的构造器可以接受任何类型的输入对象,包括File对象、InputStreamString或者Readable对象。Readable是Java SE5中新加入的一个接口,表示“具有read()方法的某种东西”。有了Scanner,所有的输入、分词以及翻译的操作都隐藏在不同类型的next方法中。普通的next()方法返回下一个String。所有的基本类型(除char之外)都有对应的next方法,包括BigDecimalBigInteger。所有的next方法,只有在找到一个完整的分词之后才会返回。Scanner还有相应的hasNext方法,用以判断下一个输入分词是否所需的类型。

13.7.1 Scanner定界符

在默认的情况下,Scanner根据空白字符对输入进行分词,但是你可以用正则表达式指定自己所需的定界符:

Scanner scanner = new Scanner("12, 42, 78, 99, 42");
scanner.useDelimiter("\\s*,\\s*);

可以用useDelimiter()来设置定界符,同时,还有一个delimiter()方法,用来返回当前正在作为定界符使用的Pattern对象。

13.7.2 用正则表达式扫描

//: strings/ThreatAnalyzer.java
import java.util.regex.*;
import java.util.*;

public class ThreatAnalyzer {
  static String threatData =
    "58.27.82.161@02/10/2005\n" +
    "204.45.234.40@02/11/2005\n" +
    "58.27.82.161@02/11/2005\n" +
    "58.27.82.161@02/12/2005\n" +
    "58.27.82.161@02/12/2005\n" +
    "[Next log section with different data format]";
  public static void main(String[] args) {
    Scanner scanner = new Scanner(threatData);
    String pattern = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@" +
      "(\\d{2}/\\d{2}/\\d{4})";
    while(scanner.hasNext(pattern)) {
      scanner.next(pattern);
      MatchResult match = scanner.match();
      String ip = match.group(1);
      String date = match.group(2);
      System.out.format("Threat on %s from %s\n", date,ip);
    }
  }
} /* Output:
Threat on 02/10/2005 from 58.27.82.161
Threat on 02/11/2005 from 204.45.234.40
Threat on 02/11/2005 from 58.27.82.161
Threat on 02/12/2005 from 58.27.82.161
Threat on 02/12/2005 from 58.27.82.161
*///:~

猜你喜欢

转载自blog.csdn.net/lanzijingshizi/article/details/84944962