java解惑之字符之谜(谜题18)

谜题18:字符串奶酪

下面这个程序从一个字节序列创建一个字符串,然后迭代遍历字符串中的字符,并将它们作为数字打印。请描述程序打印的数字序列:

public class StringCheese{
	public static void mian(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.print((int)str.charAt(i) + " ");
		}
	}
}

现在,我们来分析一下这个程序。从这个程序来看,用从0到255中每一个可能的byte数值初始化byte数组,然后将这些byte数值通过String构造器转换成char数值。最后将char数值转型为int数值并打印。打印的数值肯定是非负整数,因为char数值是无符号的,因此,我们可能会描述这个程序将按照顺序打印0到255的整数。那这样描述是否是正确的呢?

显然这样的描述是不正确的,但是我们运行程序后可能会发现确实是顺序打印了0到255的整数啊,为什么不对呢?但是你再多运行几次,就可能看到不一样的整数序列了。而书中作者在四台不同的机器上进行测试时,得到的是四个不同的序列,包括前面描述的那个序列。而且这个程序甚至不能保证正常终止,比打印其他任何特定字符串都更缺乏保证,它的行为是不确定的。产生这个问题的原因也就是我们这次要来探讨的谜题了:

String(byte[])构造器。这正是产生该谜题的罪魁祸首。有关它的规范描述:“在通过解码使用平台缺省字符集的指定byte数组来构造一个新的String时,该新String的长度是字符集的一个函数,因此,它可能不等于byte数组的长度。当给定的所有字节在缺省字符集中并非全部有效时,这个构造器的行为是不确定的”,这是在API中对该构造器的一个描述。那到底什么是字符集呢?作者讲到,从技术角度上讲,它是“被编码的字符集合和字符编码模式的结合物”,这也是来自API中的描述。通俗一点来说,字符集就是一个包,包含了字符、表示字符的数字编码以及在字符编码序列和字节序列之间来回转换的方式。转换模式在字符集之间存在着很大的区别:某些是在字符和字节之间一对一的映射,但是大多数都不是这样。ISO-8859-1是惟一能够让该程序按顺序打印从0到255的整数的缺省字符集,它更为大家所熟知的名字是Latin-1(ISO-8859-1)。

那么顺便来介绍下什么是ISO-8859-1吧,书中也没详细的介绍:

ISO-8859-1,正式编号为ISO/IEC 8859-1:1998,又称Latin-1或“西欧语言”,是国际标准化组织内ISO/IEC 8859的第一个8位字符集。它以ASCII为基础,在空置的0xA0~0xFF的范围内,加入96个字母以及符号,藉以供使用附加符号的拉丁字母语言使用。这也是维基百科中对其的一些基础介绍,想要详细了解的直接进维基百科吧:http://zh.wikipedia.org/wiki/ISO/IEC_8859-1,我们还是回归谜题的探讨吧。

J2SE运行期环境(JRE)的缺省字符集依赖于底层的操作系统和语言。如果想知道你的JRE的缺省字符集,并且使用的是5.0或更新的版本,那么你可以通过调用java.nio.charset.Charset.defaultCharset()来了解;如果使用的是较早版本,那么你可以通过阅读系统属性"file.encoding"来了解。幸运的是,没有强制要求必须容忍各种稀奇古怪的缺省字符集。在char序列和byte序列之间转换时,可以且通常应该显示地指定字符集。除了接受byte数组之外,还可以接受一个字符集名称的String构造器就是专为此目的而设计的。如果你用下面的构造器替换最初程序中的String构造器,那么不管缺省的字符集是什么,该程序都保证能够按照顺序打印从0到255的整数:

String str = new String(bytes,"ISO-8859-1");

声明这个构造器会抛出UnsupportedEncodingException异常,因此必须捕获它,或者更适宜的方式是声明main方法抛出它,否则程序不能通过编译。尽管如此,该程序实际上不会抛出异常。Charset的规范要求java平台的每一种实现都要支持某些种类的字符集,ISO-8859-1就位列其中。到此为止,我们可以从中得到的教训就是:每当要将一个byte序列转换成一个String时,你都在使用一个字符集,不管是否显示地指定了它。如果想让你的程序行为可预知,那么在每次使用字符集时都明确地指定它。

时间: 2024-12-09 06:58:49

java解惑之字符之谜(谜题18)的相关文章

java解惑之字符之谜(谜题16)

谜题16:行打印程序 行分隔符是为分割文本行的字符或字符串而起的名字,并且在不同平台上它是存在差异的.在windows平台上,它由CR字符(回车)和紧随其后的LR(换行)字符组成.在UNIX平台上,通常引用单独的LF字符作为换行字符.那么,这次的谜题也就由行分隔符引出,来看看下面这个将LF字符传递给println方法的程序会打印什么,它的行为是否依赖于平台? public class LinePrinter{ public static void main(String[] args){ //N

java解惑之字符之谜(谜题20)

谜题20:我的类是什么 来看一个打印其类文件名称的程序: package com.javapuzzlers; public class Me{ public static void main(String[] args){ System.out.println(Me.class.getName().replaceAll(".","/") + ".class"); } } 这个程序是先获得它的类名("com.javapuzzlers.Me

java解惑之字符之谜(谜题21)

谜题21:我的类是什么?镜头2 下面的程序所要做的事情正是前一个谜题所做的事情,但是它没有假设斜杠符号就是分隔文件名组成部分的符号.相反,该程序使用的是java.io.File.separetor,它被指定为一个公共的String域,包含了平台相关的文件名分隔符.这个程序会打印正确的.平台相关的类文件名吗?该程序是从这个类文件中被加载的. package com.javapuzzlers; import java.io.File; public class MeToo{ public stati

java解惑之字符之谜(谜题22)

谜题22:URL的愚弄 本谜题利用了一个java编程语言中一个鲜为人知的特性.请考虑下面的程序将会做什么? public class BrowerTest{ public static void main(String[] args){ System.out.ptintln("iexplore"); http://www.google.com; System.out.println(":maximize"); } } 这是个有点诡异的问题.当我们初次看到这个程序时,

Java解惑七:更多类之谜

谜题66 继承的问题. 对于实例方法:命名相同时,子类会覆写父类的方法,且访问权限至少和父类一样大. 对于域:命名相同时,子类会隐藏父类的域,且访问权限任意. 谜题67 不要重用库中的类名. 谜题68 命名的问题. 类名应该以大写字母开头,形式为:MixedCase. 变量以小写字母开头,形式为:mixedCase. 常量以大写字母开头,形式为:MIXED_CASE. 单个大写字母,只能用于类型参数,形式为:Map<K, V>. 包名应该都是小写,形式为:lower.case. 当一个变量和一

Java解惑五:类之谜

本文是依据JAVA解惑这本书,做的笔记.电子书见:http://download.csdn.net/detail/u010378705/7527721 谜题46 函数重载的问题. JAVA重载解析过程:1. 选取全部可用的方法或者构造器:2. 从过程1中选取的方法或构造器中选择最精确的. 一般而言:能够强制要求编译器选择一个精确的重载版本号,将实參转型为形參所声明的类型. 谜题47 继承中静态域的问题. 静态域由声明它的类及其全部子类共享. 假设须要让每个子类都具有某个域的单独拷贝,必须在每个子

Java解惑八:更多库之谜

本文是根据JAVA解惑这本书,做的笔记. 电子书见:http://download.csdn.net/detail/u010378705/7527721 谜题76 将线程的启动方法start(),写成了run(); PS:管程(monitor)锁有待进一步理解. 谜题77 线程中锁的问题. 理解不深刻. 谜题78 反射会造成访问其他包中的非公共类型的成员,引起运行期异常. 谜题79 遮蔽:Thread.sleep()方法遮蔽了自定的方法. 谜题80 反射:如何实例化非静态内部类以及静态内部类.

Java解惑六:库之谜

本文是根据JAVA解惑这本书,做的笔记. 电子书见:http://download.csdn.net/detail/u010378705/7527721 谜题56 BigInteger.BigDecimal以及包装类型的实例是不可改变. BigInteger five = new BigInteger("5"); BigInteger total = BigInteger.ZERO; total.add(five); //这并不会改变total的值,调用该方法的返回值,才是加法得到的结

Java解惑四:异常之谜

谜题36 finally语句中的return语句会覆盖掉try语句中的. 谜题37 该部分还需要进一步理解 一个方法可以抛出的被检查异常集合是它所适用的所有类型声明要抛出的被检查集合的交集. Java解惑四:异常之谜,布布扣,bubuko.com