第十三章 字符串
可以证明,字符串操作是计算机程序设计中最常见的行为。
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 整数型(十进制) 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 ) 工具:
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 |
逻辑操作符 | ||
---|---|---|
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 类之中抽象出了字符序列的一般化定义:
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 对象上的方法:
- boolean matchers() ; //判断整个输入字符串是否匹配正则表达式
- boolean lookingAt() ; //判断字符串的始部分是否能够匹配模式
- boolean find() ;
- 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 编程思想 ) 整理而来