第13章 字符串
可以证明,字符串操作是计算机程序设计中最常见的行为。
一、不可变String
1、String对象是不可变的。String类中每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动。
二、重载“+”与StringBuilder
1、String对象是不可变的,可以给String对象加任意多的别名。因为String对象具有只读特性,所以指向它的任何引用都不可能改变它的值,因此不会对其他的引用有什么影响。
2、重构,一个操作符在应用于特定的类时,被赋予了特殊的意义(用于String的“+”与“+=”)。
3、javap -c Concatenation反编译。编译器创建了stringBuilder对象,用以构建最终的String,并未每个字符中调用一次StringBuilder的append()方法,总计四次。最后调用toString()生成结果,并存为s(使用命令为astore_2)。
4、两种方法生成一个String,方法一使用了多个String对象,方法二在代码中使用了StringBuilder。
显示地创建StringBuilder还允许预先指定大小。指定大小可以避免多次重新分配缓冲。
5、 为一个类编写toString()方法时,如果字符串操作比较简单,那就信赖编译器。但是,如果你要在toString()方法中使用循环,那么最好创建一个StringBuilder对象,用它来构造最终的结果。
6、如果拿不准用哪种方法,随时可以用javap来分析你的程序。
7、StringBuilder包括insert()、replace()、substring()、reverse()、append()、toString()、delete()。
8、StringBuilder是JavaSE5引入的,之前都是用StringBuffer。后者线程安全,开销大,字符串操作更快。
三、无意识的递归
1、Java中的每个类从根本上都是继承自Object,因此容器类都有toString()方法,并且覆写了该方法,使得它生成的String结果能够表达容器自身,以及容器所包含的对象。
四、String上的操作
当需要改变字符串的内容时,String类的方法都会返回一个新的String对象。同时,若果内容没有发生改变,String的方法只是返回指向原对象的引用而已。这可以节约空间以及避免额外的开销。
五、格式化输出
1、printf():并不使用重载的“+”操作符来链接引号内的字符串或字符串变量,而是使用特殊的占位符来表示数据将来的位置。而且它还将插入格式化字符串的参数,以逗号分隔,排成一行。例如:printf("Row 1:[%d %f]\n", x, y);
格式修饰符:不但说明了插入数据的位置,还说明了将插入什么类型的变量,以及如何对其格式化。%d表示x是一个整数,%f表示y是以一个浮点数(float或者double)。
2、System.out.format():format方法可用于PrintStream或PrintWriter对象。
3、format()与 printf()是等价的,它们只需要一个简单的格式化字符串,加上一串参数即可,每个参数对应一个格式修饰符。
4、java.util.Formatter类:可以将 Formatter看作一个翻译器,它将你的格式化字符串与数据翻译成需要的结果。当你创建一个 Formatter对象的时候,需要向其构造器传递一些信息,告诉它最终的结果将向哪里输出。
Formatter构造器经过重载可以接收多种输出目的地,不过最常用的还是PrintStream()、OutputStream、File。
5、在插入数据时,如果想要控制空格与对其,需要更精细复杂的格式修饰符。语法:
%[argument_index$][flags][width][.precision]conversion
数据是右对齐,可以使用“-”,改变对其方向。
width可以应用于各种类型的数据转换,并且其行为方式都一样。 precision则不然,不是所有类型的数据都能使用 precision。在将 precision应用于 String时,它表示打印String时输出字符的最大数量。而在将precision应用于浮点数时,它表示小数部分要显示出来的位数(默认是6位小数),小数位数过多则舍入,太少则在尾部补零。所以 precision无法应用于整数。
6、Formatter转换:
程序中的每个变量都用到了b转换。虽然它对各种类型都是合法的。对于 boolean基本类型或 Boolean对象,其转换结果是对应的true或 false。但是,对其他类型的参数,只要该参数不为null,那转换的结果就永远都是true。将b应用于非布尔类型的对象时请格外小心。
7、String.format()是一个static方法,它接受与 Formatter.format()方法一样的参数,但返回一个 String对象。当你只需使用 format()方法一次的时候, String. format()用起来很方便。
8、在 String. format()内部,它也是创建一个 Formatter对象,然后将你传入的参数转给该Formatter。不过,与其自己做这些事情,不如使用便捷的 String. format()方法,何况这样的代码更清晰易读。
9、String. format()方法,以可读的十六进制格式将字节数组打印出来:
六、正则化表达式
1、在java中,字符串操作还主要集中于String、StringBuffer、StringTokenizer类。正则表达式是一种强大灵活的文本处理工具。是一种简洁、动态的语言。提供了一种完全通用的方式,能够解决各种字符串处理相关的问题:匹配、选择、编辑以及验证。
2、-?\\d+:表示一个或多个之前的表达式。“可能有一个负号,后面跟着一位或多位数字”。
(-|\\+)?:表示可以以一个加号或减号开头。
正则表达式工具:split()将字符串从正则表达式匹配的地方切开。还有重载你的版本,允许限制字符串分割的次数。
3、正则表达式的完整构造子列表。
4、量调描述了一个模式吸收输入文本的方式:
(1)贪婪型:量词总是贪婪的,除非有其他的选项被设置,贪婪表达式会为所有可能的模式发现尽可能多的匹配,导致此问题的一个典型理由就是假定我们的模式仅能匹配第一个可能的字符组,如果它是贪婪的,那么它就会继续往下匹配。
(2)勉强型:用问号来指定,这个量词匹配满足模式所需的最少字符数。因此也称作懒情的最少匹配的、非贪婪的,或不贪婪的。
(3)占有型:目前,这种类型的量词只有在Java语言中才可用,并且也更高级。因此我们大概不会立刻用到它。当正则表达式被应用于字符串时,它会产生相当多的状态,以便在匹配失败时可以回溯。而“占有的”量词并不保存这些中间状态,因此它们可以防止回溯。它们常常用于防止正则表达式失控,因此可以使正则表达式执行起来更有效。
5、接口CharSequence从CharBuffer、String、StringBuffer、StringBuilder类之中抽象出了字符序列的一般化定义:
因此,这些类都实现了该接口,多数正则表达式操作都接收CharSequence类型的参数。
6、Pattern和Matcher:一般来说,比起功能有限的 String类,我们更愿意构造功能强大的正则表达式对象。只需导入 java.util.regex包,然后用 static Pattern. compile()方法来编译你的正则表达式即可。它会根据你的 String类型的正则表达式生成一个 Pattern对象。接下来,把你想要检索的字符串传入Pattern对象的 matcher方法。 matcher0方法会生成一个 Matcher对象。
static boolean matches(String regex, CharSequence input)
该方法用以检查 regex,是否匹配整个 CharSequence类型的 input参数,编译后的 Pattern对象还提供了 split()方法,它从匹配了 regex的地方分割输入字符串,返回分割后的子字符串 String数组。
通过调用 Pattern. matcher方法,并传入一个字符串参数,我们得到了一个 Matcher对象使用 Matcher上的方法,我们将能够判断各种不同类型的匹配是否成功:
其中matches()方法用来判断整个输入字符串是否匹配正则表达式模式,而 lookingAt()则用来判断该字符串(不必是整个字符串)的始部分是否能够匹配模式。
7、Matcher.find():可用来在CharSequence中查找多个匹配。find()像选代器那样前向遍历输入字符串。而第二个find()能够接收一个整数作为参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。从结果中可以看出,后一个版本的find()方法能根据其参数的值,不断重新设定搜素的起始位置。
8、组( Groups):组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为0表示整个表达式组号1表示被第一对括号括起的组,依此类推。因此,在下面这个表达式,A(B(C))D 中有三个组:组0是ABCD,组1是BC,组2是C。
9、start()与end():在匹配操作成功之后, start()返回先前匹配的起始位置的索引,而end()返回所匹配的最后字符的索引加一的值。匹配操作失败之后(或先于一个正在进行的匹配操作去尝试)调用start()或end()将会产生 IllegalStateException。
10、find可以在输入的任意位置定位正则表达式,而 lookingAt()和 matches()只有在正则表达式与输入的最开始处就开始匹配时才会成功。 matches()只有在整个输入都匹配正则表达式时才会成功,而 lookingAt()只要输入的第一部分匹配就会成功。
11、Pattern标记:Pattern类的compile()方法还是另一个版本,它接收一个标记参数,以调整匹配的行为:Pattern Pattern.compile(String regex, int flag)
12、split():将输入字符串断开成字符串对象数组,断开边界由下列正则表达式表示:
String[] split(CharSequence input)
String[] split(CharSequence input, int limit)
这是一个快速而方便的方法,可以按照通用便捷断开输入文本:
13、正则表达式特别便于替换文本:
replaceFirst(string replacement)以数字符串 replacement 替换掉第一个匹配成功的部分。
replaceAll( String replacement)以参数字符串 replacement 替换所有匹配成功的部分。
appendReplacement( Stringbuffer shuf, String replacement)执行渐进式的替换,而不是像 replaceFirst()和 replaceAll() 那样只替换第一个匹配或全部匹配。这是一个非常重要的方法,它允许你调用其他方法来生成或处理 replacement( replace First和 replaceable则只能使用一个固定的字符串),使你能够以编程的方式将目标分割成组,从而具备更强大的替换功能。
appendTall( StringBuffer sbuf),在执行了一次或多次appendReplacement()之后,调用此方法可以将输入字符串余下的部分复制到sbuf中。
14、reset()方法,可以将现有的Matcher对象应用于一个新的字符序列。使用不带参数的reset()方法,可以将Matcher对象重新设置到当前字符序列的起始位置。
15、一个文件中进行搜索匹配操作。 JGrep java的灵感源自于Unix上的grep。它有两个参数:文件名以及要匹配的正则表达式。输出的是有匹配的部分以及匹配部分在行中的位置。
16、通过net.mindview.util.Textfile对象将文件打开,读入所有的行后,并存储在一个 Arraylist中。因此,可以用循环来选代遍历 Textfile对象中的所有行。
七、扫描输入
1、从文件或标准输入读取数据,一般的解决之道就是读入一行文本,对其进行分词,然后使用 Integer、 Double等类的各种解析方法来解析数据:
2、Scanner类,大大减轻扫描输入的工作负担。
Scanner的构造器可以接受任何类型的输入对象,包括Fie对象、 Inputstream、 String或者 Readable对象。 Readable是Java SE5中新加入的一个接口,表示“具有 read()方法的某种东西”。
有了 Scanner,所有的输入、分词以及翻译的操作都隐藏在不同类型的next方法中。普通的next()方法返回下一个 String。所有的基本类型(除char之外)都有对应的next方法,包括 Bigdecimal 和 Biginteger。所有的next方法,只有在找到一个完整的分词之后才会返回。Scanner还有相应的 hasnext方法,用以判断下一个输入分词是否所需的类型。
Betterread.java没有针对 IOException添加try区块。因为, Scanner有一个假设,在输入结束时会抛出IOException,所以 Scanner会把Ioexception吞掉。不过,通过 IOException方法,你可以找到最近发生的异常。因此,你可以在必要时检查它。
3、在默认情况下,Scanner根据空白字符对输入进行分词,但是你可以用正则表达式指定自己所需的定界符:
可以用来读取逗号分隔的文件。可以用useDelimiter()来设置定界符,同时,还有一个delimiter()方法,用来返回当前作为定界符使用的Pattern对象。
4、使用自定义的正则表达式进行扫描,这在扫描复杂数据的时候非常有用。
八、String Tokenizer
在Java引入正则表达式(J2SE1.4)和 Scanner类( Java SE5)之前,分割字符串的唯一方法是使用 String Tokenizer来分词。现在不用了。