《Java编程思想》笔记13-字符串

重载 "+" 与 StringBuilder

使用 JDK自带的工具 javap 来反编译。javap -c 文件名,这里的 -c 表示将生成 JVM 字节码。

使用重载 "+",发现编译器会创建一个 StringBuilder 对象,每次 "+" 会调用一次 StringBuilder 的 append() 方法,最后调用 toString() 方法生成结果。但如果在循环中,每次循环将会生成新的 StringBuilder 对象。

String s = "";
for (int i=0; i<10; i++>) {
  s+=i; // 相当于 StringBuilder sb=new StringBuilder(); sb.append(i);
}

直接使用 StringBuilder,只会生成一个对象,还可以预先指定大小,避免多次重新分配缓冲。

无意识的递归

如果想要在 toString() 方法中打印出对象的地址,可能会使用 this 关键字,会造成递归。所以应该调用 super.toString() 方法。

格式化输出

printf()

System.out.format()System.out.printf()。这两个是等价的。类似于 C语言的 printf()。

System.out.println("Row 1: ["+x+" "+y+" "+z+"]\n");
System.out.printf("Row 1: [%d %f %s]\n", x, y, z);
System.out.format("Row 1: [%d %f %s]\n", x, y, z);

Formatter

java.util.Formatter,可以看做是一个翻译器,它将你的格式化字符串于数据翻译成需要的结果。

Formatter 的构造器通过重载可以接受多种输出目的地,最常用的还是 PrintStream(),OutputStream 和 File。

格式化说明符

可以控制空格与对齐。

%[argument_index$][flags][width][.precision][conversion]

  • argument_index: 一个正整数。标明是第几个参数。"1$" 表示第一个参数 arg1, "2$" 表示第二个参数 arg2,以此类推。
  • flags: 格式符。比较常用的几个是:"-" 表示左对齐。" " 空格表示右对齐,前面用空格填满。"0" 右对齐,前面用0填满。
  • width: 一个正整数。表示这个域的最小尺寸。不足用空格来补。
  • precision: 精度。对不同数据类型的意义不同。对 String 表示字符串的最大长度 %.10s。对 float 和 double 则表示小数部分有几位。不能应用于整数。
  • conversion: 转换成什么数据类型来输出。之前的 %d 这样的占位符,其实就是中间其他参数省略的结果,最后的 conversion 直接跟在 % 后面。
public class Receipt {
  private double total = 0;
  private Formatter f = new Formatter(System.out);
  public void printTitle() {
    f.format("%-15s %5s %10s\n", "Item", "Qty", "Price");
    f.format("%-15s %5s %10s\n", "----", "---", "-----");
  }
  public void print(String name, int qty, double price) {
    f.format("%-15.15s %5d %10.2f\n", name, qty, price);
    total += price;
  }
  public void printTotal() {
    f.format("%-15s %5s %10.2f\n", "Tax", "", total*0.06);
    f.format("%-15s %5s %10s\n", "", "", "-----");
    f.format("%-15s %5s %10.2f\n", "Total", "",
      total * 1.06);
  }
  public static void main(String[] args) {
    Receipt receipt = new Receipt();
    receipt.printTitle();
    receipt.print("Jack's Magic Beans", 4, 4.25);
    receipt.print("Princess Peas", 3, 5.1);
    receipt.print("Three Bears Porridge", 1, 14.29);
    receipt.printTotal();
  }
} /* Output:
Item              Qty      Price
----              ---      -----
Jack's Magic Be     4       4.25
Princess Peas       3       5.10
Three Bears Por     1      14.29
Tax                         1.42
                           -----
Total                      25.06
*///:~

conversion

转换 参数类别 说明
b, B 常规 如果参数为 null,则结果为 "false",否则结果为 "true"。
h, H 常规 结果为调用 Integer.toHexString(arg.hashCode()) 得到的结果。
s, S 常规 String
c, C 字符 结果是一个 Unicode 字符
d 整数 十进制整数
o 整数 八进制整数
x, X 整数 十六进制整数
e, E 浮点 用计算机科学记数法表示的十进制数
f 浮点 十进制数
t, 'T' 日期/时间 日期和时间转换字符的前缀。
% 百分比 结果为字面值 '%' ('\u0025')
n 行分隔符 结果为特定于平台的行分隔符

String.format()

接受与 Formatter.format() 方法一样的参数。

String.format("(t%d, q%d) %s", 3, 7, "Write failed");
// new Formatter().format(format, args).toString() // 内部如此实现
// (t3, q7) Write failed

正则表达式

语法

完整的语法定义参见 java.util.regex.Pattern 类的官方文档。

正则表达式最常用到的符号就是三个表示数量的符号了:

  • ?:出现零次或一次。
  • *:出现零次或一次或多次。就是不管出现不出现,也不管出现多少次。
  • +:至少出现一次或多次。就是出现至少一次。
  • {n}:准确地出现n次。
  • {n,}:至少出现n次。
  • {n,m}:至少出现n次,但至最多出现m次。

这些表示数量的符号要结合表示“字符”的符号一起使用。正则表达式语法里定义了很多,举几个最常用的例子:

  • 1:正常阿拉伯数字,就表示它本身。
  • x:正常英语字母,就表示他们本身。
  • [abc]:a、b 或 c(简单类)
  • [^abc]:任何字符,除了 a、b 或 c(否定)
  • [a-zA-Z]:a 到 z 或 A 到 Z,两头的字母包括在内(范围)
  • [a-d[m-p]]:a 到 d 或 m 到 p:[a-dm-p](并集)
  • [a-z&&[def]]:d、e 或 f(交集)
  • [a-z&&[^bc]]:a 到 z,除了 b 和 c:[ad-z](减去)
  • [a-z&&[^m-p]]:a 到 z,而非 m 到 p:[a-lq-z](减去)

预定义字符类

  • .:任何字符
  • \d:任意一个[0-9]的阿拉伯数字。
  • \D:除了[0-9]数字之外的其他所有字符。
  • \s:一个空白符。包括[ \t\n\x0B\f\r]这些。这个真的很好用。
  • \S:相反的除了空白符之外的所有字符。
  • \w:一个单词字符。就是英语大小写字母加阿拉伯数字零到九,注意还有一个下划线_也算在里面。[a-zA-Z_0-9]
  • \W:除了单词字符以外的全部其他字符。
  • \b:单词边界符。这比较神奇。只匹配一个单词的边界,不匹配任何字符。占零个长度。这里的单词,就是前面\w单词符的内容。
  • \B:判断不是单词边界。
  • \G:前一个匹配的结束。

还有其他的一些格式符:

  • \t:一个tab符。对应的ASKII码是:('\u0009')
  • \n:一个换行符。对应的ASKII码是:('\u000A')
  • \r:一个回车符。对应的ASKII码是:('\u000D')
  • \e:一个空格符。对应的ASKII码是:('\u001B')
  • \xhh:带有十六进制值 0x 的字符 hh
  • \uhhhh:带有十六进制值 0x 的 Unicode 字符 hhhh
  • ^:行首符。必须在正则表达式的起始位置。
  • $:行尾符。也可以是文末符,表示一段文本的末尾。

正则表达式用括号括起来的部分都是一个组。每个组在正则表达式里,都有自己的序号。序号是这样定义的: 假设有 A(B(C))D 这个正则表达式,一共有三个组:

  1. Group 0:就是全体 ABCD
  2. Group 1:就是左起第一个括号里的内容 BC
  3. Group 2:是左起第二个括号里的 C

Pattern

在 java.util.regex 包里的 Pattern 类和 Matcher 类才是专门为正则表达式而生的类。

  1. static Pattern compile(String regex) 可以把 String 形式的正则表达式编译成一个 Pattern 对象。
  2. public static Pattern compile(String regex, int flags) 将给定的正则表达式编译到具有给定标志的模式中。
  3. Matcher matcher(CharSequence input) 传递一个需要匹配的字符串,返回一个 Matcher 对象。
  4. static boolean matches(String regex, CharSequence input) 用以检查 regex 是否匹配整个 CharSequence 类型的 input 参数。
  5. public String[] split(CharSequence input) 围绕此模式的匹配拆分给定输入序列。
  6. public String[] split(CharSequence input, int limit)(limit 参数控制应用模式的次数,从而影响结果数组的长度。如果 n 大于零,数组的长度不大于 n,并且数组的最后条目将包含除最后的匹配定界符之外的所有输入。如果 n 非正,那么将应用模式的次数不受限制,并且数组可以为任意长度。如果 n 为零,非正基础上,并且将丢弃尾部空字符串。)

Matcher

  1. boolean matches() 判断整个输入字符串是否匹配正则表达式模式(完全匹配)。

  2. boolean lookingAt() 判断字符串的起始部分是否能够匹配模式(与 matches() 方法类似,只是不需要完全匹配,只需开头部分匹配)。

  3. boolean find() 尝试查找与该模式匹配的输入序列的下一个子序列。此方法从匹配器区域的开头开始,如果该方法的前一次调用成功了并且从那时开始匹配器没有被重置,则从以前匹配操作没有匹配的第一个字符开始。如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。

  4. boolean find(int start) 重置此匹配器(每次调用都会重置),然后尝试查找匹配该模式,从指定索引开始的输入序列的下一个子序列。

public class Finding {
  public static void main(String[] args) {
    Matcher m = Pattern.compile("\\w+")
      .matcher("Evening is full");
    while(m.find())
      printnb(m.group() + " ");
    print();
    int i = 0;
    while(m.find(i)) {
      printnb(m.group() + " ");
      i++;
    }
  }
} /* Output:
Evening is full
Evening vening ening ning ing ng g is is s full full ull ll l
*///:~
  1. int groupCount() 返回此匹配器模式中的捕获组数。第0组不包括在内。
  2. String group(int group) 返回在以前匹配操作期间由给定组捕获的输入子序列。对于匹配器 m、输入序列 s 和组索引 g,表达式 m.group(g) 和 s.substring(m.start(g), m.end(g)) 是等效的。
  3. String group() 表达式 m.group(0) 等效于 m.group()。
  4. int start(int group) 返回匹配到的子字符串在字符串中的索引位置。
  5. int start() 表达式 m.start(0) 等效于 m.start()。
  6. int end(int group) 返回匹配到的子字符串的最后一个字符在字符串中的索引位置。
  7. int end() 表达式 m.end(0) 等效于 m.end()。
public class Groups {
  static public final String POEM =
    "Twas brillig, and the slithy toves\n" +
    "Did gyre and gimble in the wabe.\n" +
    "The frumious Bandersnatch.";
  public static void main(String[] args) {
    // 练习12:匹配小写 \\b[^A-Z\\s][a-z]+
    Matcher m = Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$").matcher(POEM);

    while(m.find()) {
      for(int j = 0; j <= m.groupCount(); j++)
        printnb("[" + m.start(j) + " - " + m.group(j) + " - " + m.end(j) + "]");
      print();
    }
  }
} /* Output:
[18 - the slithy toves - 34][18 - the - 21][22 - slithy toves - 34][22 - slithy - 28][29 - toves - 34]
[55 - in the wabe. - 67][55 - in - 57][58 - the wabe. - 67][58 - the - 61][62 - wabe. - 67]
[68 - The frumious Bandersnatch. - 94][68 - The - 71][72 - frumious Bandersnatch. - 94][72 - frumious - 80][81 - Bandersnatch. - 94]
*///:~

Pattern标记

  1. Pattern.CANON_EQ 启用规范等价。当且仅当完全规范分解相匹配时,就认为它们是匹配的,指定此标记a\u030A就会匹配?
  2. Pattern.CASE_INSENSITIVE(?i) 启用不区分大小写的匹配。
  3. Pattern.COMMENTS(?x) 模式中允许空白和注释。空白和以#开始的注释都会被忽略
  4. Pattern.DOTALL(?s) 启用 dotall 模式。.可以匹配任何字符,包括行结束符。默认情况下,此表达式不匹配行结束符。
  5. Pattern.LITERAL 启用模式的字面值解析。指定此标志后,输入序列中的元字符或转义序列不具有任何特殊意义。
  6. Pattern.MULTILINE(?m) 启用多行模式。仅分别在行结束符前后匹配,或者在输入序列的结尾处匹配。默认情况下,这些表达式仅在整个输入序列的开头和结尾处匹配。
  7. Pattern.UNICODE_CASE(?u) 启用 Unicode 感知的大小写折叠。
  8. Pattern.UNIX_LINES(?d) 启用 Unix 行模式。在此模式中,.、^ 和 $ 的行为中仅识别 ‘\n’ 行结束符。

Pattern.CASE_INSENSITIVEPattern.MULTILINEPattern.COMMENTS这三个标记特别有用。compile(String regex, int flags) 中的标记 flags 还可以用或(|)来组合多个标记的功能

替换操作

  1. String replaceFirst(String replacement) 首先重置匹配器,以参数字符串 replacement 替换掉第一个匹配成功的部分。

  2. String replaceAll(String replacement) 首先重置匹配器,以参数字符串 replacement 替换掉所有匹配成功的部分。

  3. Matcher appendReplacement(StringBuffer sb, String replacement) 执行渐进式的替换。

Pattern p = Pattern.compile("cat");
Matcher m = p.matcher("one cat two cats in the yard");
StringBuffer sb = new StringBuffer();
while (m.find()) {
    m.appendReplacement(sb, m.group().toUpperCase());
}
m.appendTail(sb);
System.out.println(sb.toString()); // one CAT two CATs in the yard
  1. StringBuffer appendTail(StringBuffer sb) 此方法从添加位置开始从输入序列读取字符,并将其添加到给定字符串缓冲区。可以在一次或多次调用 appendReplacement 方法后调用它来复制剩余的输入序列

  2. Matcher reset() 重置匹配器。

  3. Matcher reset(CharSequence input) 重置匹配器,并应用于新的字符串序列。

Scanner 类

一个可以使用正则表达式来解析基本类型和字符串的简单文本扫描器。

Scanner 的构造器可以接受任何类型的输入对象,如 File 对象,InputStream,String 对象。

public class BetterRead {
  public static BufferedReader input = new BufferedReader(new StringReader("zz\n22 1.61803"));
  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?
zz
How old are you? What is your favorite double?
(input: <age> <double>)
22
1.61803
Hi zz.
In 5 years you will be 27.
My favorite double is 0.809015.
*///:~

IOException ioException() 返回此 Scanner 的底层 Readable 最后抛出的 IOException。如果不存在这样的异常,则此方法返回 null。

Scanner 定界符

public class ScannerDelimiter {
  public static void main(String[] args) {
    Scanner scanner = new Scanner("12, 42, 78, 99, 42");
    scanner.useDelimiter("\\s*,\\s*");
    while(scanner.hasNextInt())
      System.out.println(scanner.nextInt());
  }
} /* Output:
12
42
78
99
42
*///:~

Scanner useDelimiter(String pattern) 将此扫描器的分隔模式设置为从指定 String 构造的模式。
此方法调用 useDelimiter(pattern) 的行为与调用 useDelimiter(Pattern.compile(pattern)) 完全相同。

Pattern delimiter() 返回此 Scanner 当前正在用于匹配分隔符的 Pattern。

用正则表达式扫描

除了能够扫描基本类型之外,你还可以使用自定义的正则表达式进行扫描,这在扫描复杂数据是非常有用。

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
*///:~


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:《Java编程思想》笔记13-字符串

文章字数:3.8k

本文作者:Bin

发布时间:2018-05-01, 15:18:45

最后更新:2019-08-06, 00:46:42

原始链接:http://coolview.github.io/2018/05/01/Java%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3/%E3%80%8AJava%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3%E3%80%8B%E7%AC%94%E8%AE%B013-%E5%AD%97%E7%AC%A6%E4%B8%B2/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录