第十三章 字符串

可以证明,字符串操作是计算机程序设计中最常见的行为。


1. 不可变 String

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

    package Thirteen;
    
    public class OneTest {
      public  static String upcase(String s){
          return s.toUpperCase() ;
      }
    
      public static void main(String[] args) {
          String q = "thirteen" ;
          System.out.println(q);
          String qq = upcase(q) ;
          System.out.println(qq);
          System.out.println(q);
      }
    }
    ////output
    thirteen
    THIRTEEN
    thirteen
  • 当把 q 传给 upcase() 方法的时候,实际传递的是引用的一个拷贝。其实每当把 String 对象作为方法的参数的时候,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未动过。
  • 传入 upcase() 的引用有了名字 s ,只有 upcase() 运行的时候,局部引用 s 才存在,一旦 upcase() 运行结束, s 就消失了。 upcase() 的返回值,也只是最终结果的引用。这些都足以说明, upcase() 返回的引用已经指向了一个新的对象,而原本的 q 其实还在原地。
  • 对一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的。

2. 重载 “ + ” 与 SringBuilder

  • String 对象是不可变的 ,你可以给一个 String 对象加任意多的别名。 因为 String 对象具有只读特性,所以指向它的任何引用都不可能改变它的值,因此,也就不会对其他的引用有什么影响,
  • 重载的意思是:一个操作符在应用于特定的类时,被赋予了特殊的意义。(用于 String 的 “ + ” 与 “ += ” 是 java 中仅有的两个重载过的操作符,而 java 并不允许程序员重载任何操作符 )
  • 通过 “ + ” 操作符 连接 String:

    package Thirteen;
    public class TwoTest {
      public static void main(String[] args) {
          String mango = "mango" ;
          String s = "abc" + mango + "def" + 47 ;
          System.out.println(s) ;
      }
    }
    ///output
    abcmangodef47
  • 可以使用 JDK 自带的工具 javap 来反编译以上代码: javap -c TwoTest , -c 表示将生成 JVM 字节码。
  • 当使用 “ + ” 的时候,会自动使用 StringBuilder 类。为每个字符串调用一次 StringBuilder 的 append() 方法。最后调用 toString() 生成结果。
  • 使用 StringBuilder 还允许你预先为其指定大小,如果想在 toString() 方法中使用循环,那么最好自己创建一个 StringBuilder 对象,用它来构造最终的结果

    package Thirteen;
    import java.util.Random;
    public class ThreeTest {
      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()-1 , result.length()) ;
          result.append("]") ;
          return result.toString() ;
      }
      public static void main(String[] args) {
          ThreeTest tts = new ThreeTest() ;
          System.out.println(tts);
      }
    }
    /// 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]
  • StringBuffer 提供了丰富全面的方法。包括: insert() , repleace() , substring() , 甚至 reverse() , 但是最常用的还是 append() , toString() ,还有 delete() 方法。

3. 无意识的递归

  • java 中的每个类从根本上都是继承自 Object , 标准类容器也不例外,因此容器类都有 toString() 方法,并且覆写了这个方法,使得生成的 String 结果能够表达容器本身,以及容器所包含的对象。

    ......
    return "xxxx " + this ;
    .....
  • 并不会返回想要的结果,因为编译器看到前面是一个 String 类型,后面跟一个 “ + ” ,会尝试把 this 换成 String 类型。想正确调用 this 需要调用 super.toString() 方法。

4. String 上的操作

以下是 String 对象的一些基本方法。

方法应用
构造器创建 String 对象
length()String 中字符的个数
charAt()取得 String 中该索引位置上的 char
getChars(),getBytes()复制 char 或者 byte 到一个目标数组中
toCharArray()生成一个 char[] , 包含 String 的所有字符
equals(),equalsIgnoreCase()比较两个 String 内容 是否相同
compareTo()按词典顺序比较 String 的内容,比较结果为负数、零或正数。注意,大小写并不等价
contains()如果该 String 对象包含参数的内容,则返回 true
contentEquals()如果该 String 与参数内容完全一致,则返回 true
equalsIgnoreCase()忽略大小写,如果两个 String 内容相同,则返回 true
regionMatcher()返回 boolean 结果,已表明所比较区域是否相等
startsWith()返回 boolean 结果,以表明该 String 是否 以此参数开始
endsWith()返回 boolean 类型结果,以表明此参数是否该字符串的后缀
indexOf(),lastIndexOf()如果该 String 并不包含此参数,就返回 -1 ;否则返回此参数在 String 中的起始索引。lastIndexOf() 是从后向前搜索
substring(subSequence())返回一个新的 String ,以包含参数指定的子字符串
concat()返回一个新的 String 对象,内容为原始 String 连接上参数 String
replace()返回替换字符后的新 String 对象。如果替换没有发生,则返回原始的 String 对象
toLowerCasetoUpperCase()将字符的大小写改变以后,返回一个新的 String 对象,如果没有改变发生,则返回原始的 String 对象
trim()将 String 两端的空白字符删除后,返回一个新的 String对象, 如果改变没有发生,则返回原始的 String 对象
vaueOf()返回一个表示参数内容的 String
intern为每个唯一的字符序列生成一个且仅生成一个 String 引用
  • 可以看出,当 需要改变字符串的内容时,String 类的方法都会返回一个新的 String 对象。如果没有发生改变, String 的方法只是返回指向原对象的引用而已,这可以节约存储空间以及避免额外的开销。

murong mengjie, [27.06.18 09:36]

5. 格式化输出

5.1. printf()

  • printf() 虽然不使用重载的 “ + ” 操作符,但是可以使用特殊的 占位符来表示数据将来的位置,还可以将插入格式化字符串的参数。这些占位符叫做 格式修饰符

printf("Row 1: [%d %f\n]" , x,y ) ;


5.2. System.out.format()

  • format 方法可用于 PrintStream 或者 PrintWriter 对象,其中也包括 System.out 对象。 format() 方法模仿自 C 的 printf()
  • format() 和 printf() 是等价的,他们只需要一个简单的格式化字符串,加上一串参数即可,每个参数对应一个格式修饰符。

5.3. Formatter 类

  • 所有的格式化功能都由 java.util.Formatter 处理,可以将 Formatter 看做一个翻译器,他将你的格式化字符串与数据翻译成需要的结果。

5.4. 格式化说明符

  • 如果想要控制空格与对齐等等,需要更加精细复杂的格式修饰符,同样是使用的 Formatter

    //语法:
    %[argument_index$][flags][width][.precision]conversion
    

5. 5. Formatter 转换

  • 下面的表格包含了最常用的类型转换:

    类型转换字符
    d整数型(十进制)
    cUnicode字符
    bboolean 值
    sString
    f浮点数(十进制)
    e浮点数(科学计数)
    x正数(十六进制)
    h散列码(十六进制)
    %字符“ % ”
  • 对于 b 转换,基本类型是 boolean 基本类型或者 Boolean 对象,其转换结果是对应的 true 或者 false ,但是对于其他类型的参数,只要不为 null ,全部都是 true ,即便是0 。

5. 6. String.format

  • String.format() 是一个 static 方法,接受和 Formatter。format() 方法一样的参数,但是返回一个 String 对象。
  • 相当于 C 中的 sprintf() 方法。
  • 一个十六进制转储( dump ) 工具:

    package Thirteen;
    import java.io.File;
    public class HexTest {
      public static String format(byte[] date){
          StringBuilder result = new StringBuilder() ;
          int n = 0 ;
          for(byte b : date){
              if(n % 16 == 0){
                  result.append(String.format("%05X: ",n)) ;
              }else{
                  result.append(String.format("%02X : " ,b)) ;
              }
              n++ ;
              if(n % 16 == 0){
                  result.append("\n") ;
              }
          }
          result.append("\n");
          return result.toString() ;
      }
      public static void main(String[] args) throws Exception{
          if(args.length == 0){
              System.out.println(format(BinaryFile.read("xxx.class")));
          }else{
              System.out.println(format(BinaryFile.read(new File(args[0]))));
          }
      }
    }

6.正则表达式

  • 是用正则表达式可以使我们以编程的方式,构造复杂的文本模式,并对输入的字符串进行搜索。

6.1 基础

  • 一般来说,正则表达式就是以某种方式来描述字符串。也就是:如果一个字符串包含有这些东西,那么就正是我在找的东西。
  • 找一个负号在前面的数字 : -?
  • 用 \d 表示 一位数字
  • 在 其他语言中 , \\ 表示我想在正则表达式中插入一个普通的 反斜线,请不要给它任何特殊的意义。在 java 中 \\ 的意思是我要插入一个正则表达式的反斜线,所以其后面的字符具有特殊的意义。比如像表示一位数字,那么正则表达式应该是:\\d ,如果想插入一个普通的反斜线,那么应该写成:\\\\ 不过换行和制表符之类的东西只需使用单反斜线: \n\t 。
  • 可能有一个负号,后面跟着一位或多位数字: -?\\d+
  • 应用正则表达式的最简单的途径,就是使用 String 类内建 的 功能。

    package Thirteen;
    public class IntegerMatch {
      public static void main(String[] args) {
          System.out.println("-1234".matches("-?\\d+"));
          System.out.println("5678".matches("-?\\d+"));
          System.out.println("+564".matches("-?\\d+"));
          System.out.println("+911".matches("(-|\\+)?\\d+"));
      }
    }
    // output
    true
    true
    false
    true
  • 可能以一个加号或者减号开头,在正则表达式中,括号有着将表达式分组的效果,而竖直线 | 则表示或操作: ( -|\\+ )? ,表示字符串的起始字符可能是一个 - 或者 + 或者两个都没有,因为跟着 ? 修饰符 , 因为 + 在正则表达式中有着特殊的意义,所以必须使用转义字符 \\ 将其转义为普通字符
  • split() 方法 ,功能是将字符串从正则表达式匹配的地方切开
  • replaceFirst() 方法,只替换正则表达式第一个匹配的字串
  • replaseAll() ,替换所有匹配的地方。
  • \W 意思是非单词字符
  • \w 意思是一个单词字符。
  • f\\w+ 以字母 f 开头,后面跟 一个或多个字母。

6.2 创建正则表达式

字符
B指定字符 B
\xhh十六进制值为 oxhh 的字符
\uhhhh十六进制表示为 oxhhhh 的 Unicode 字符
\t制表符 Tab
\n换行符
\r回车
\f换页
\e转义(Escape)
字符类
.任意字符
[abc]包含a.b.c的任何字符(和a\b\c作用相同)
1除了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空白符(空格、tab、换行、换页、回车)
\S非空白符(2)
\d数字[0-9]
\D非数字3
\w词字符[a-zA-Z0-9]
\W非词字符4
逻辑操作符
XYY 跟在 X 后面
X\YX 或 Y
(X)捕获组(capturing group)
边界匹配符
^一行的起始
$一行的结束
\b词的边界
\B非词的边界
\G前一个匹配的结束

6.3. 量词

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

  • 贪婪型:量词总是贪婪的。贪婪表达式会为所有可能的模式发现尽可能多的匹配。
  • 勉强型:用问号来指定,这个量词匹配满足模式所需的最少字符数,因此也被称作懒惰的、最少匹配的、不贪婪的。
  • 占有型:防止正则表达式失控。
  • 表达式应该用圆括号括起来,以便他能够按照我们期望的效果去执行。例如: abc+ ,看起来是:匹配一个或多个 abc 序列,但实际上表示的是:匹配 ab ,后面跟随一个或者多个 c。正确的应该这样写:( abc ) +
  • CharSequence ,接口 CharSequence 从 CharBuffer 、String 、 StringBuffer 、 StringBuilder 类之中抽象出了字符序列的一般化定义:

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

6.4 Pattern 和 Matcher

  • 根据传入的 String 类型 生成一个 Pattern 对象。将要检索的字符串传入 Pattern 对象的 ,matcher() 方法。 matcher() 方法会生成一个 Matcher 对象。
    java
    Pattern p = Pattern.compile(arg) ;
    Matcher m = p.matcher(args[0]) ;
  • Matcher 对象上的方法:

    1. boolean matchers() ; //判断整个输入字符串是否匹配正则表达式
    2. boolean lookingAt() ; //判断字符串的始部分是否能够匹配模式
    3. boolean find() ;
    4. boolean find(int start) ;
  • Matcher.find() 方法可以用来在 CharSequence 中查找多个匹配。
  • \\w+ 可以将字符串划分为单词。

    package Thirteen;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    public class IntegerMatch {
      public static void main(String[] args) {
          Matcher m =Pattern.compile("\\w+").matcher("Evening is full of the linnet");
          while(m.find()){
              //  像迭代器那样前向遍历输入字符串
              System.out.println(m.group()+"");
          }
          System.out.println();
          int i = 0 ;
          while(m.find(i)){
              //接受一个整数作为参数,整数表示字符串中字符的位置,以此作为搜索的起点
              System.out.println(m.group()+"");
              i++ ;
          }       
      }
    }
  • 组(Group) 是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为 0 表示整个表达式,组号 1 表示第一队括号括起来的组。以此类推。
    A ( B ( C ) ) D
    三个组 ,组0 是 ABCD , 组1 是 BC ,组 2是 C
  • 在匹配操作成功后, strat() 返回先前匹配的起始位置的索引, end () 返回尝试所匹配的最后字符的索引加一的值。

6.5 split()

  • split() 方法将输入字符串断开成字符串对象数组,断开边界由正则表达式确立。

6.6 替换操作

  • 正则表达式特别便于替换文本,提供了很多的方法。

7. 扫描输入

  • Scanner类的构造器可以接受任何类型的输入对象。
  • 默认情况下,Scanner 根据空白字符对输入的内容进行分词,但是你可以用正则表达式指定自己所需的定界符。

    Scanner scanner = new Scanner("12,13,14");
    scanner.useDelimit("\\s*,\\s*") ;
  • 使用正则表达式:
    java
    Scanner scanner = new Scanner("xxx");
    String pattern = "(\d+[.]\d+[.])" ;
    while(scanner.hasNext(pattern)){
    /// Code
    }


8. String Tokenizer

  • 分隔字符串,不过有了 Scanner 之后已经废弃掉了。

由于时间原因,这本书之后就没再做什么笔记了


由本人从 Thinking in java ( java 编程思想 ) 整理而来


  1. abc
  2. \s
  3. 0-9
  4. \w
最后修改:2018 年 07 月 15 日
哇卡哇卡