》》不可变String
@@ String 对象是不可变的。
@@ String 类中每一个看起来会修改 String 值的方法,实际上都是创建了一个全新的
String 对象,以包含修改后的字符串内容。而最初的 String 对象则丝毫未动。
@@ 每当把 String 对象作为方法的参数时,都会复制一份引用,而该引用所指的对象
其实一直待在单一的物理位置上,从未动过。
@@ 例如:
public String upcase( String s){
return s.toUpperCase();
}
说明:上面的 upcase () 的返回值,其实只是最终结果的引用。这足以说明,
upcase () 的引用已经指向了一个新的对象。
@@ 对于一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的。
》》重载 ”+“ 与 StringBuilder
@@ String 对象是不可改变的,你可以给一个String 对象加任意多个别名。因为 String
对象具有只读特性,所以指向它的任何引用都不可能改变它的值,因此,也就不会
对其他的引用有什么影响。
@@ 用于 String 的 “+”与 “+=” 是 Java 中仅有的两个重载过的操作符,而 Java 并不允许
程序员重载任何操作符。
@@ 操作符 “ + ” 可以用来连接 String 。
@@ 如果想看到 java 代码是如何工作的,可以用 JDK 自带的工具 javap 来反编译 java
代码。
@@ 需要重点注意的是:编译器会自动引入了 java.lang.StringBuilder 类。虽然我们在
源码里面并没有使用 StringBuilder 类,但是编译器却自作主张地使用了它,因为它
更高效。
@@ 编译器创建了一个 StringBuilder 对象,用以构造最终的 String ,并为每个字符串调用
一次 StringBuilder 的 append() 方法。最后调用 toString() 生成结果。
@@ 使用 StringBuilder 对象 可以优化字符串的处理性能。
@@ 显式的创建 StringBuilder 对象允许你预先为其指定大小。如果你已经知道最终的字符串
大概有多长,那预先指定 StringBuilder 的大小可以避免多次重新分配缓冲。
@@ 当你为一个类编写 toString() 方法时,如果字符串操作比较简单,那就可以信赖编译器,
它会为你合理地构造最终的字符串结果。但是,如果你要在 toString() 方法中使用循环,
那么最好自己创建一个 StringBuilder 对象,用它来构造最终的结果。
例如:
public 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.append("]");
return result.toString();
}
@@ 如果拿不准该用哪种方式,随时可以用 javap 来分析你的程序。(通过反编译来找合适的思路)。
》》无意识的递归
@@ Java 的每个类从根本上都是继承自 Object ,标准容器类也不例外。因此容器类都有 toString()
方法,并且覆写了该方法,使得它生成的 String 结果能够表达容器自身,以及容器所包含的对象。
例如:
ArrayList.toString() 它会遍历 ArrayList 中包含的所有对象,调用每个元素上的 toString() 方法。
@@ 如果想要 toString() 方法打印出对象的内存地址,应该调用的是:Object.toString() 方法。
》》String 上的操作
@@ 以下是一些 String 对象具备的一些基本方法:
从上面这个表中可以看出,当需要改变字符串的内容时,String 类的方法都会返回一个新的
String 对象。同时,如果内容没有发生改变,String 方法只是返回指向原对象的引用而已。这样
可以节约存储空间以及避免额外的开销。
》》格式化输出
## printf()
@@ 使用特殊的占位符来表示数据将来的位置,而且它还将插入格式化字符串的参数,以逗号分隔,
排成一行。
例如:
printf(" Row1 : [%d %f] \n" , x , y )
说明: 上面这行代码在运行的时候,首先将 x 的值插入到 %d 的位置,然后将 y 的值插入到 %f
的位置。%d , %f 这些占位符称作格式修饰符,它们不但说明了插入数据的位置,同时还说明了将
插入什么类型的变量,以及如何对其格式化。这上面的例子中, %d 表示 x 是一个整数, %f 表示
y 是一个浮点数(float 或者 double)
## System.out.format()
@@ format() 方法可用于 PrintStream 或者 PrintWriter 对象,其中也包括 System.out 对象。
@@ format() 与 printf() 是等价的。
例如:
printf(" Row1 : [%d %f] \n" , x , y );
format(" Row1 : [%d %f] \n" , x , y ) ;
## Formatter 类
@@ 在Java 中,所有新的格式化功能都由 java.util.Formatter 类处理。可以将 Formatter 看作一个
翻译器,它将你的格式化字符串与数据翻译成需要的结果。当你创建 Formatter 对象的时候,
需要向其构造器传递一些信息,告诉它最终的结果将向哪里输出。
@@ new Formatter(System.out)
表明输出将输出到 System.out
@@ Formatter 的构造器经过重载可以接受多种输出目的地,不过最常用的还是 PrintStream 、
OutputStream 、 File
## 格式化说明符
@@ 在插入数据时,如果想要控制空格与对齐,你需要更精细复杂的格式修饰符。以下是其抽象
的语法:
%[argument_index$] [flags] [width] [.precision] conversion
@@ 最常见的应用是一个域的最小尺寸,这可以通过指定 width 来实现。
@@ Formatter 对象通过在必要时添加空格,来确保一个域至少达到某个长度。
@@ 在默认情况下,数据是右对齐,不过可以使用 “ - ” 标志来改变对齐方向。
@@ 使用 precision 来指明最大尺寸。
@@ width 可以用于各种类型的数据转换,并且其行为方式一样。
@@ precision 与 width 不同:
---- 不是所有的数据都能使用 precision , 而且,应用于不同类型的数据转换时,precision
的意义也不同
---- 将 precision 应用于 String 时,它表示打印 String 时输出字符的最大数量。
---- 将 precison 应用于浮点数时,它表示小数部分要显示出来的位数(默认是 6 位数),如果
小数位数过多则舍入,太少则在尾部补零。
---- 整数没有小数部分,所以 precision 无法应用于整数,如果你对整数应用 precision ,
则会触发异常。
## Formatter 转换
@@ 下面表格中包含了最常用的类型转换:
补充:----- 将 b 应用于非布尔类型的对象时请格外小心。
------ 还有许多不常用的类型转换与格式修饰符选项,可以在 JDK 文档中 Formatter
类部分找到
## String.format()
@@ String.format() 是一个 static 方法,它接受与 Formatter.format () 方法一样的参数,但
返回一个 String 对象。当你只需要使用 format() 方法一次的时候,String.format() 用起来
很方便。
@@ 其实在 String.format() 内部,它也是创建一个 Formatter 对象,然后将你传入的参数转给
该 Formatter 。
@@ 为了打开以及读入二进制文件,我们用到另一个工具 net.mindview.util.BinaryFile
》》正则表达式
@@ 在Java 中,字符串操作还主要集中于 String 、 StringBuffer 和 StringTokenizer 类。与正则
表达式相比较,它们只能提供相当简单的功能。
@@ 正则表达式是一种强大而灵活的文本处理工具。
@@ 使用正则表达式,我们能够以编程的方式,构造复杂的文本模式,并对输入的字符串进行搜索。
一旦找到匹配这些模式的部分,你就能够随心所欲地对它们进行处理。
@@ 正则表达式提供了一种完全通用的方式,能够解决各种字符串处理相关的问题:匹配 、
选择 、 编辑以及验证。
## 基础
@@ 一般来说,正则表达式就是以某种方式来描述字符串。
@@ 要找一个数字,它可能有一个负号在最前面,那你就写一个负号加上一个问号,就像下面:
-?
@@ 如果想表示一位数字,那么正则表达式应该是 \\d 。
@@ 如果你想插入一个普通的反斜线,那么正则表达式应该是 \\\\ 。
@@ 换行 , 正则表达式为 \n (使用单反斜线)。
@@ 制表符 ,正则表达式为 \t (使用单反斜线) 。
@@ 要表示 “ 一个或多个之前的表达式 ” ,应该使用 + 。
@@ 例如:
System.out.println("-1234".matches("-?\\d+"));
System.out.println("+911".matches("(-|\\+)?\\d+"));
说明:a ) 、 (-|\\+)?
---- 可能以一个加号或减号开头
---- 在正则表达式式中,括号有着表达式分组的效果,而竖直线 | 则表示或操作。
---- (-|\\+)? 这个正则表达式表示字符串的起始字符可能是一个 - 或 + ,或二者皆没有
(因为后面跟着? 修饰符)。因为字符 + 在正则表达式中有特殊的意义,所以必须
使用 \\ 将其转义,使之成为表达式中的一个普通字符。
@@ String 类还自带一个非常有用的正则表达式工具--------split() 方法,其功能是“ 将字符串从正则
表达式匹配的地方切开 ” 。
例如:
public static void main(String[] args){
split(" "); // 按空格来划分字符串
split("\\W+");
split("n\\W+"); // 字母 n 后面跟着一个或多个非单词字符
}
说明: 后面两个都用了 \W , 它的意思是非单词字符 (如果 W 小写, \w 则表示一个单词字符)
@@ String.split() 还有一个重载的版本,它允许你限制字符串分割的次数。
@@ String 类自带的最后一个正则表达式工具是“ 替换 ” 。你可以只替换正则表达式第一个匹配的子串,
或是替换所有匹配的地方。
例如:
staitc String s = Splitting.knights;
public static void main(String[] args){
// 字母 f 开头,后面跟一个或多个字母(注意这里是 w 小写的),并且只能替换掉第一个匹配
的部分
print( s.replaceFirst( "f\\w+" , 'located' ) ) ;
// 下面表达式要匹配的是三个单词中的任意一个,因为它们以竖直线分隔表示 “或” ,并且替换
所有匹配的部分
print( s.replaceAll( " shrubbery | tree | herring " , 'located' ) ) ;
}
@@ String 之外的正则表达式还有更强大的替换工具。例如,可以通过方法调用执行替换。而且,如果
正则表达式不是只使用一次的话,非 String 对象的正则表达式明显具备更佳的性能。
## 创建正则表达式
@@ 正则表达式的完整构造子列表,参考 JDK 文档 java.util.regex 包中的 Pattern 类
@@ 字符
@@ 字符类(学会使用字符类之后,正则表达式的威力才能真正显示出来)。下面是一些
创建字符类的典型方式,以及一些预定义的类:
@@ 逻辑操作符
@@ 边界匹配符
@@ 我们的目的不是编写最难理解的正则表达式,而是尽量编写能够完成任务的、最简单
以及最必要的正则表达式。一旦真正开始使用正则表达式了,你就会发现,在编写新的
表达式之前,你通常会参考代码中已经用到的正则表达式。
## 量词
@@ 量词描述了一个模式吸收输入文本的方式:
贪婪型:量词总是贪婪的,除非有其他的选项被设置。贪婪表达式会为所有可能的模式
发现尽可能多的匹配。导致此问题的一个典型理由就是假定我们的模式仅能匹配
第一个可能的字符组,如果它是贪婪的,那么它就会继续往下匹配。
勉强型:用问号来指定,这个量词匹配满足模式所需的最少字符数。因此也称为懒惰的、
最少匹配的、非贪婪的、或不贪婪的。
占有型:目前,这种类型的量词只有在 Java 语言中才可用(在其他语言中不可用),并且
也更高级,因此我们大概不会立刻用到它。当正则表达式被应用于字符串时,它会
产生相当多的状态,以便在匹配失败时可以回溯。而 “占有的 ” 量词并不保存这些中
间状态,因此它们可以防止回溯。它们常常用防止正则表达式失控,因此可以使正则
表达式执行起来更有效。
@@
补充:应该非常清楚地意识到,表达式 X 通常必须要用圆括号括起来,以便它能按照我们
期望的效果去执行。例如:
(abc)+
@@ 正则表达式是一种在 Java 之上新语言。
@@ 接口 CharSequence 从 CharBuffer 、 String 、 StringBuffer 、 StringBuilder 类之中抽象出
了字符序列的一般化定义。
因此,这些类都实现了该接口。多数正则表达式操作都接受 CharSequence 类型的参数。
## Pattern 和 Matcher
@@ 一般来说,比起功能有限的 String 类,我们更愿意构造功能强大的正则表达式对象。只需要
导入 java.util.regex 包,然后用 static Pattern.compile() 方法(生成一个 Pattern 对象)
来编译你的正则表达式即可。
它会根据你的 String 类型的正则表达式生成一个 Pattern 对象。
把你想要检索的字符串传入 Pattern 对象的 matcher() 方法。matcher() 方法会生成一个 Matcher
对象,它有很多功能可用(参考 java.util.regex.Matcher 的JDK 文档)
@@ 在 Unix / Linux 上,命令行中的正则表达式必须用引号括起来。
@@ Pattern 对象表示编译后的正则表达式。
@@ Pattern 类还提供了 static 方法:
static boolean matches(String regex , CharSequence input)
该方法用以检查 regex 是否匹配整个 CharSequence 类型的 input 参数 。
@@ 编译后的 Pattern 对象还提供了 split() 方法,它从匹配了 regex 的地方分割输入
字符串,返回分割后的子字符串 String 数组。
@@ 通过调用 Pattern.matcher() 方法,并传入一个字符串参数,我们得到一个 Matcher 对象。
使用 Matcher 上的方法,我们将能够判断各种不同类型的匹配是否成功:
boolean matchers() // 用来判断整个输入字符串是否匹配正则表达式模式
boolean lookingAt() // 用来判断该字符串(不必是整个字符串)的始部分是否
能够匹配模式
boolean find()
boolean find(int start)
@@ 例如:
public static void main(String[] args){
Matcher m = Pattern.compile("\\w+").matcher("Evening is full of the linnet's wings");
while(m.find()){
printnb(m.group() + " ") ;
}
print();
int i = 0 ;
while(m.find(i)){
printnb(m.group() + " ") ;
i++;
}
}
说明:(1)、\\w+ 将字符串划分为单词。
(2)、 find() 就像迭代器一样前向遍历输入字符串。
(3)、 find( i ) 接受一个整数作为参数,该整数表示字符串中字符的位置,并以其
作为搜索的起点。
(4) m.group() ----》组
----- 组是括号划分的正则表达式,可以根据组的编号来引用某个组。组号为 0 表示
整个表达式,组号为 1 表示被第一对括起的组,依次类推。例如:
A ( B ( C ) ) D
里面有三个组:组 0 是 ABCD , 组 1 是 BC , 组 2 是 C
@@ Matcher 对象提供了一系列方法,用以获取与组相关的信息。
------ public int groupCount() 返回该匹配器的模式中的分组数目,第 0 组不包括在内。
------ public String group( ) 返回前一次匹配操作(例如 find( ) )的第 0 组(整个匹配)。
------ public String group( int i ) 返回在前一次匹配操作期间指定的组号,如果匹配成功,
但是指定的组没有匹配输入字符串的任何部分,则将会返回
null
------- public int start( int group ) 返回在前一次匹配操作中寻找到的组的起始索引
------- public int end( int group ) 返回在前一次匹配操作中寻找到的组的最后一个字符索引加一
的值
@@ 一定要显式地告知正则表达式注意输入序列中的换行符。这可以由序列开头的模式标记(?m)
来完成。
@@ start() 与 end()
----- 在匹配成功之后,start () 返回先前匹配的起始位置的索引,
end() 返回所匹配的最后字符的索引加一的值
----- 在匹配失败之后(或先于一个正在进行的匹配操作去尝试),调用 start() 或 end() 将会产生
IIIegalStateException 。
@@ 注意:
----- find() 可以在输入的任意位置定位正则表达式
---- lookingAt() 和 matchers() 只有在正则表达式与输入的最开始处开始匹配时才会成功。
《》 matches() 只有在整个输入都匹配正则表达式时才会成功
《》 lookingAt() 只要输入的第一部分匹配就会成功
@@ Pattern 标记(模式标记)
----- Pattern 类的 compile() 方法还有另外一个版本,它接受标记参数,以调整匹配的行为:
Pattern Pattern.compile(String regex , int flag);
其中的 flag 来自以下 Pattern 类中的常量(如下)
------
说明:(1)、在这些标记中, Pattern.CASE_INSENSITIVE 、 Pattern.MULTILINE
以及 Pattern.COMMENTS (对声明或文档有用)特别有用。
(2)、你可以直接在正则表达式中使用其中的大多数标记,只需要将上表中括号
括起来的字符插入到正则表达式中,你希望它起作用的位置即可。
(3)、可以通过 “或 ”(|) 操作符组合多个标记功能。
@@ group() 方法只返回已匹配的部分。
## split()
@@ split() 方法将输入字符串断开成字符串对象数组,断开边界由下列正则表达式确定:
String[] split(CharSequence input)
String[] split(CharSequence input , int limit)
这是一个快速而方便地方法,可以按照通用边界断开输入文本。
@@ 例如:
String input = "This!!unusual use !! of exclamation !! options ";
Pattern.compile("!!").split(input);
Pattern.compile("!!").split(input , 3 ); // 这个方法可以限制将输入分割成字符串的数量
## 替换操作
@@ 正则表达式特别便于替换文本,它提供了许多方法:
------ replaceFirst(String replacement ) 以参数字符串 replacement 替换掉第一个匹配成功的部分
------ replaceAll(String replacement ) 以参数字符串 replacement 替换掉所有匹配成功的部分
----- appendReplacement( StringBuffer sbuf , String replacement ) 执行渐进式的替换
%% 不是像 replaceFirst() 和 replaceAll() 那样只替换第一个匹配或全部匹配
%% 允许你调用其他方法来生成或处理 replacement (replaceFirst() 和 replaceAll() 则只能使
用一个固定的字符串),使你能以编程的方式将目标分割成组,从而具备更强大的替换功能。
----- appendTail( StringBuffer sbuf ) , 在执行了一次或多次 appendReplacement () 之后,调用此方法
可以将输入字符串余下的部分复制到 sbuf 中。
@@ TextFile 类( 该类在 net.mindview.util 工具包中)
------ static read() 方法读入整个文件,将其内容作为 String 对象 返回。
@@ replaceFirst() 和 replaceAll() 方法用来替换的只是普通的字符串,所以,如果你想对替换字符串
执行某些特殊的处理,这两个方法时无法胜任的。
@@ 如果想执行某些特殊的处理,可以考虑使用 appendReplacement() 方法,该方法允许你在执行
替换的过程中,操作用来替换的字符串
@@ appendReplacement() 方法允许你通过 $g 直接找到匹配的某个组,这里的 g 就是组号。然而,
它只能应付一些简单的处理。
## reset()
@@ 通过 reset() 方法,可以将现有的 Matcher 对象应用于一个新的字符序列。
@@ 例如:
Matcher m = Pattern.compile("[frb][aiu][gx]").matcher("fix the rug with bags") ;
m.reset("fix the rig with rags"); // 带参数的
@@ 使用不带参数的 reset() 方法,可以将 Matcher 对象重新设置到当前字符序列的起始位置。
## 正则表达式与 Java I/O
@@ 之前的内容讲的都是:将正则表达式应用于静态的字符串
@@还可以将正则表达式应用在一个文件中进行搜索匹配。
》》扫描输入
@@ 从文件或标准输入读取数据,一般的解决方法是:读入一行文本,对其进行分词。
@@ 实现(读入一行文本)代码:
public static BufferReader input = new BufferReader( new StringReader( " ddssddsasfgda") ) ;
String name = input.readLine(); // 一次读取一行文本
System.out.println(name);
说明:(1)、BufferReader 类来自 java.io 包
(2)、 StringReader 将 String 转化为可读的流对象,然后用这个对象来构造 BufferReader
(3)、readLine() 方法将一行输入转为 String 对象。如果每一行数据正好对应一个输入值,那
这个方法也还可行。但是,如果两个输入值在同一行中,事情就不好办了,我们必须分解
这个行,才能分别翻译所需要的输入值。
@@ Scanner 类
----- Scanner 的构造器可以接受任何类型的输入对象,包括 File 对象 、 InputStream 、String 、
Readable 对象 (是一个接口,表示“具有 read() 方法的某种东西 ” ,BufferReader 也归于这
类)
------ 有了 Scanner ,所有的输入 、 分词以及翻译的操作都隐藏在不同类型的 next() 方法中。
%% 普通的 next() 方法返回下一个 String
%% 所有的基本类型(除 char 之外)都有对应的 next() 方法,包括 BigDecimal 和 BigInteger
%% 所有的 next() 方法,只有在找到一个完整的分词之后才会返回。
%% Scanner 还有相应的 hasNext() 方法,用以判断下一个输入分词是否所需的类型
------- 因为,Scanner 有一个假设,在输入结束时会抛出 IOException ,所以 Scanner 会把
IOException 吞掉。不过,通过ioException() 方法,你可以找到最近发生的异常,因此,
你可以在必要时检查它。
## Scanner 定界符
@@ 在默认情况下, Scanner 根据空白符对输入进行分词
@@ 可以用正则表达式指定自己所需要的定界符:
public staitc void main(String[] args){
Scanner scanner = new Scanner("12, 42, 78, 99, 42");
scanner.useDelimiter("\\s*,\\s*") // 使用逗号(包括逗号前后任意的空白符)作为
定界符
}
说明:(1)、scanner.useDelimiter("\\s*,\\s*") 除了可以读取字符串,还可以用来读取逗号
分隔的文件。
(2)、使用 useDelimiter( ) 来设置定界符
(3)、 delimiter() 方法,用来返回当前正在作为定界符使用的 Pattern 对象
## 用正则表达式扫描
@@ 除了能扫描基本数据类型之外,还可以使用自定义的正则表达式进行扫描,这在扫描复杂数据
的时候非常有用。
@@ 例如:
public static void main(String[] args){
Scanner scanner = new Scanner("sdfdgdssd");
String pattern = "\\d+[.]" // 字符串里面是正则表达式
while(scanner.hasNext(pattern)){
scanner.next(pattern);
MatchResult match = scanner.match();
String ip = match.group(1);
String date = match.group(2);
System.out.println(date, ip );
}
}
说明:(1)、当 next( ) 方法配合使用指定的正则表达式使用时,将找到下一个匹配该模式
的输入部分
(2)、调用 match( ) 方法就可以获得匹配的结果
(3)、在配合使用正则表达式使用扫描时,有一点需要注意:它仅仅针对下一个输入
分词进行匹配,如果你的正则表达式中含有定界符,那永远不可能匹配成功。
》》 StringTokenizer
@@ 在 Java 引入正则表达式和Scanner 类之前,分割字符串的唯一方法是使用 StringTokenizer 来
分词。
》》总结
@@ 在对字符串操作的支持,要在细节上注意效率的问题,例如恰当地使用 StringBuilder 等。