Loading... # 第十三章 字符串 **可以证明,字符串操作是计算机程序设计中最常见的行为。** ----- ### 1. 不可变 String * String 对象是不可变的。String 类中的每一个看起来会修改 String 值得方法,实际上都是创建了一个全新的 String 对象,以包含修改后的字符串内容。而最初的 String 对象则丝毫未动。 ```java 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: ```java 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 对象,用它来构造最终的结果 ```java 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 结果能够表达容器本身,以及容器所包含的对象。 ```java ...... 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 ```java //语法: %[argument_index$][flags][width][.precision]conversion ``` --- ####5. 5. Formatter 转换 * 下面的表格包含了最常用的类型转换: |类型转换字符|| |--|--| |d|整数型(十进制)| |c|Unicode字符| |b|boolean 值| |s|String| |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 ) 工具: ```java 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 类内建 的 功能。 ```java 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作用相同)| |[^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|空白符(空格、tab、换行、换页、回车)| |\S|非空白符([^\s])| |\d|数字[0-9]| |\D|非数字[^0-9]| |\w|词字符[a-zA-Z0-9]| |\W|非词字符[^\w]| |逻辑操作符|| |:--|:--| |XY|Y 跟在 X 后面| |X\|Y|X 或 Y| |(X)|捕获组(capturing group)| |边界匹配符|| |:--|:--| |^|一行的起始| |$|一行的结束| |\b|词的边界| |\B|非词的边界| |\G|前一个匹配的结束| --- ####6.3. 量词 量词描述了一个模式吸收输入文本的方式 * 贪婪型:量词总是贪婪的。贪婪表达式会为所有可能的模式发现尽可能多的匹配。 * 勉强型:用问号来指定,这个量词匹配满足模式所需的最少字符数,因此也被称作懒惰的、最少匹配的、不贪婪的。 * 占有型:防止正则表达式失控。 * 表达式应该用圆括号括起来,以便他能够按照我们期望的效果去执行。例如: abc+ ,看起来是:匹配一个或多个 abc 序列,但实际上表示的是:匹配 ab ,后面跟随一个或者多个 c。正确的应该这样写:( abc ) + * CharSequence ,接口 CharSequence 从 CharBuffer 、String 、 StringBuffer 、 StringBuilder 类之中抽象出了字符序列的一般化定义: ```java 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+ 可以将字符串划分为单词。 ```java 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 根据空白字符对输入的内容进行分词,但是你可以用正则表达式指定自己所需的定界符。 ```java 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 编程思想 ) 整理而来 最后修改:2018 年 07 月 15 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 哇卡哇卡