《java解惑》读书笔记2——字符串之谜

来源:互联网 发布:db2数据库的一些操作 编辑:程序博客网 时间:2024/05/19 22:24

1.字符拼接:

问题:

程序员几乎在每天编程中都遇到和处理字符串拼接的问题,但是是否对其了解的足够深入,且看下面的程序:

public static void main(String[] args) {System.out.print("H" + "a");System.out.print('H' + 'a');}
很多人觉得输出结果应是:HaHa,但是真实的程序运行结果是:Ha169。

原因:

程序第一个输出是两个字符串拼接,所以结果毫无疑问。

第二个输出是字符相加而非字符串拼接,因此会使用字符的ASCII码(H是72,a是97)进行相加然后将char类型的结果进行数据类型拓宽为int类型输出,因此输出结果是169.

结论:

很多人认为字符串和字符是一样的,其实这是个错误认识,字符char是个无符号的16位整数,而字符串是将一系列字符以unicode方法处理。

因此,对于字符拼接可以使用下面的方法:

方法一:

使用StringBuffer,StringBuilder等的append方法拼接字符,代码如下:

StringBuffer sb = new StringBuffer();sb.append('H');sb.append('a');System.out.println(sb);
方法二:

在字符拼接中拼接中以一个空的字符串开头,代码如下:

System.out.println("" + 'H' + 'a');
方法三:

使用格式化的打印输出printf,代码如下:

System.out.printf("%c%c", 'H', 'a');

方法四:

使用字符串API-String.valueOf(char c),代码如下:

System.out.printf(String.valueOf('H') + String.valueOf('a'));

2.字符数组打印:

问题:

很多人使用下面的小程序打印字符数组:

public static void main(String[] args) {String letters = "ABC";char[] numbers = {'1', '2', '3'};System.out.println(letters + " easy as " + numbers);}
本来期望程序输出结果为:ABC easy as 123,但是真实程序运行结果类似为:ABC easy as [C@1db05b2。

原因:

尽管字符是一个16位的无符号整数类型,但是它通常表示的是字符而不是整数,因此很多程序对其进行了特殊处理,输出其Unicode字符而非其ASCII码,System.out.println,String.valueOf和StringBuffer.append会将字符数组中所包含的所有字符打印输出,而字符串链接操作符+没有在这些方法中定义,因此会现将要拼接的两个操作数转换为字符串(调用toString方法),然后执行字符串拼接,因此字符数组在没有重写toString方法时,将会打印其Object的默认toString,由此产生上述的结果。

结论:

解决上述问题有如下方法:

方法1:

将字符串分开打印,使用JDK API的重载方法,代码如下:

public static void main(String[] args) {String letters = "ABC";char[] numbers = {'1', '2', '3'};System.out.print(letters + " easy as ");System.out.print(numbers);}
方法2:

使用String.valueOf(char[])方法将字符数组转化为字符串,代码如下:

public static void main(String[] args) {String letters = "ABC";char[] numbers = {'1', '2', '3'};System.out.print(letters + " easy as " + String.valueOf(numbers));}
当字符数组和字符串进行拼接时,一定千万注意,很多时候结果并不是我们所期望的。


3.字符串比较问题:

测测一下下面程序的输出结果:

public static void main(String[] args) {final String pig = "length: 10";final String dog = "length: " + pig.length();System.out.print("Animals are equal:" + pig == dog);}
有人可能觉得应该输出:Animals are equals:true,因为dog字符串也是“length: 10”,并且两个字符串都是final类型的,java中相同的字符串会保存在字符串常量池中,因此是同一个对象,因此比较应该为true。

也有人可能局的应该输出:Animals are equals:false,因为java中字符串是不可变的,dog字符串是由两个字符拼接的,隐私dog是另一个对象,因此比较应该为false。

但是程序真实运行结果为:false,没有Animals are equals:字符串打印出来。

原因:

+不论用做加法还是字符串拼接运算符,它都比==操作符优先级更高,因此上面的println语句是按照如下的方式运算:

System.out.print(("Animals are equal:" + pig) == dog);
这个表达式当然输出的结果是false。

结论:

解决上面问题的方法有:

方法1:

使用括号显式指定操作符优先级,代码如下:

public static void main(String[] args) {final String pig = "length: 10";final String dog = "length: " + pig.length();System.out.print("Animals are equal:" + (pig == dog));}

该程序输出结果为:Animals are equals:false。

但是这种做法使用了JVM字符串常量池限制,如果dog字符串直接使用了字符串常量池,则有可能会输出Animals are equals:true的结果,程序不应该依赖与JVM字符串常量池限制,因此不推荐使用该方法。

方法2:

使用equals方法比较字符串,代码如下:

public static void main(String[] args) {final String pig = "length: 10";final String dog = "length: " + pig.length();System.out.print("Animals are equal:" + pig.equals(dog));}
该程序输出结果为:Animals are equals:true。

当对字符串比较时,最好使用equals方法,除非是比较两个对象是否相等时才使用==,另外使用==时特别要注意运算符优先级。


4.转义字符:

问题:

下面的程序输出结果应该是什么:

public static void main(String[] args) {System.out.print("a\u0022.length() + \u0022b".length());}
\u0022是unicode转义字符双引号,有人觉得应该输出26,因为在两个双引号直接有26个字符,有人觉得是16,因为一个转义字符\u0022表示一个字符。

真实程序运行结果为:2。

原因:

将\u0022是转义字符替换为双引号,在上面程序可以被替换为如下代码:

public static void main(String[] args) {System.out.print("a".length() + "b".length());}
因此程序运行结果为2.

如果期望输出16,则使用如下代码:

public static void main(String[] args) {System.out.print("a\".length() + \"b".length());}
结论:

java中使用有很多普通转义字符:单引号(\'),换行(\n),制表符(\t),反斜线(\\)等,使用转义字符比使用Unicode的转义字符更清楚,因此在程序中优先选用普通转义字符。


5.注释中的特殊字符:

注意Java注释中特殊字符,例如”\u“表示Unicode转义字符开始。

特别注意”\u“不会出现在一个Unicode转义字符上下文之外,注释中也要特别注意,除非特别必要,否则尽量不要使用Unicode转义字符。


6.字符串的编码问题:

问题:

猜猜下面的程序运行结果是什么:

public static void main(String[] args){byte bytes[] = new byte[256];for(int i = 0; i < 256; i++){bytes[i] = (byte)i;}String str = new String(bytes);for(int i = 0, n = str.length(); i < n; i++){System.out.println((int)str.charAt(i) + " ");}}
程序的逻辑很简单,先将从0到255的正整数放入一个字节数组中,然后将字节数组转换为字符串,最后再将字符串中每位字符转换为int数值打印出来,所以程序应该打印出从0到255的数值。

很遗憾告诉你真正程序的运行结果不可预测,甚至程序不能正常终止。

原因:

java的字符串构造函数String(byte[])规范中规定:在通过解码使用平台缺省字符集的指定byte数组来构造一个新的String时,该新String的长度是字符集的一个函数,因此字符串的长度可能不等于byte数组的长度,当给定的所有字节在缺省字符集中并非全部有效时,这个构造器的行为是不确定的。

字符集从技术角度来讲是被编码的字符集合和字符编码模式的结合物,也就是说字符集和一个包,包含了字符、表示字符的数字编码以及在字符编码序列和字节编码序列之间相互转换的方式,转换模式在字符集之间存在着很大的区别:某些是在字符和字节之间做一对一的映射,但是大多数字符集都不是这样。ISO-8859-1是唯一能让上述程序按顺序打印从0到255的整数的缺省字符集。

结论:

将程序显式指定ISO-8859-1字符编码集,即可让程序达到我们期望的结果,代码如下:

public static void main(String[] args){byte bytes[] = new byte[256];for(int i = 0; i < 256; i++){bytes[i] = (byte)i;}String str;try {str = new String(bytes, "ISO-8859-1");for(int i = 0, n = str.length(); i < n; i++){System.out.println((int)str.charAt(i) + " ");}} catch (UnsupportedEncodingException e) {e.printStackTrace();}}
如果不相信可以指定UTF-8,GBK等字符编码集,程序都不能顺序打印从0到255的整数。

J2SE运行是环境JRE的缺省字符编码集依赖于底层操作系统和语言,在java中获取默认字符编码集方法:

JDK5之前的版本:使用System.getProperty("file.encoding")方法来获取。

JDK5以后的版本:可以使用java.nio.charset.Charset.defaultCharset()方法来获得。

每当需要将一个byte序列转换成一个String时,不论是否显式指定字符编码集,程序都在使用某一字符集,如果想让程序的行为可预知,那么最好在每次使用的时候都显式明确第指定所使用的字符编码集。