2009年12月31日星期四

Java中的float&double以及IEEE754研究

1、IEEE754二进制浮点数算术标准,全称ANSI/IEEE Std 754-1985


浮点数:有理数中某些特定自己的数的数字表示,在计算机中用来近似表示任意某个实数。这个实数由一个整数或定点数乘以某个技术的整数次幂得到,

类似于基数为10的科学计数法。

一个浮点数a由两个数m和e来表示:a=m*b^e。选择一个基数b和精度p。m即尾数是形式如d.ddddd的p位数,每一位介于0~b-1之间的数,小数点左侧的数字不为 0。

浮点运算伴随着因为无法精确表述而进行的近似或舍入。

浮点数标准:浮点数标准是关于浮点数存储和计算的标准。在多数情况下它是指IEEE 754标准(包括单精度浮点数、双精度浮点数等的格式和运算规定)。

在IEEE标准754之前,业界并没有一个统一的浮点数标准。很多计算机制造商都设计自己的浮点数规则,以及运算细节。那时,实现的速度和简易性比数字的

精确性更受重视。这给代码的可移植性造成了障碍。直到1985年,Intel打算为其的8086微处理器引进一种浮点数协处理器的时候,聘请了加州大学伯克利分校

的William Kahan教授──最优秀的数值分析家之一来为8087 FPU设计浮点数格式;William Kahan又找来两个专家来协助他,于是就有了KCS组合(Kahn, Coonan, and Stone),并共同完成了Intel的浮点数格式设计。Intel的KCS浮点数格式完成得如此出色,以致于IEEE决定采用一个非常接近KCS的方案作为IEEE的标准浮点格式。IEEE于1985年制订了二进制浮点运算标准(Binary Floating-Point Arithmetic)IEEE 754。该标准限定指数的底为2。同年,被美国引用为ANSI标准。目前,几乎所有计算机都支持该标准,大大改善了科学应用程序的可移植性。考虑到IBM System/370的影响,IEEE于1987年推出了与底数无关的二进制浮点运算标准IEEE 854。同年,被美国引用为ANSI标准。1989年,国际标准组织IEC批准IEEE 754/854为国际标准IEC 559:1989。后来经修订后,标准号改为IEC 60559。现在,几乎所有的浮点处理器完全或基本支持IEC 60559。C99的浮点运算也支持IEC 60559。

IEEE二进位浮点数算术标准(IEEE 754)是浮点数运算标准,为许多CPU与浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零-0)与反常值

(denormal number),一些特殊数值(无穷与非数值NaN),以及这些数值的“浮点数运算子”;它也指明了四种数值修约规则和五种例外状况(包括例外发生的时机

与处理方式)。

IEEE 754规定了四种表示浮点数值的方式:单精确度(32位元)、双精确度(64位元)、延伸单精确度(43位元以上,很少使用)与延伸双精确度



(79位元以上,通常以80位元实做)。只有32位元模式有强制要求,其他都是选择性的。大部分语言都有提供IEEE格式与算术,但有些将其列为非必要的。

例如,IEEE 754问世之前就有的C语言,现在有包括IEEE算术,但不算作强制要求(C语言的float通常是指IEEE单精确度,而double是指双精确度)。

该标准的全称为IEEE二进位浮点数算术标准(ANSI/IEEE Std 754-1985),又称IEC 60559:1989,微处理器系统的二进位浮点数算术(本来的编号是IEC 559:1989)

。后来还有“与基数无关的浮点数”的“IEEE 854-1987标准”,有规定基数为2跟10的状况。

历史来源,参考《IEEE754:An Interview with William Kahan》







2、二进制浮点数的表示

二进制浮点数以二号数值表示法格式存储,将最高位指定为符号位(Sign bit),次高的e位指定为指数位(Exponent Bias),小数部分(decimal Fraction)

即最后剩下的f位。有效位数=符号位+指数位+小数位。

S+Exp+Fraction

32位单精度

32=1+8+23,偏正值+127。

单精度的指数部分是-126~+127加上127 ,指数值的大小从1~254(0和255是特殊值)。浮点小数计算时,指数值减去偏正值将是实际的指数大小。

例如:

3F400000=0.75

0=正数;1111110=126,指数=126-127=-1;

1 .10000000000000000000000=2^0+*2^-1=1.5,小数部分=1.5

也就是1.5*2^-1=0.75



数值的计算:





特殊数值的表示:















64位双精度

64=1+11+52,偏正值+1023

双精度的指数部分是1022~+1023加上1023 ,指数值的大小从1~2046(0(2进位全为0)和2047(2进位全为1)是特殊值)。浮点小数计算时,指数值减去偏正值将是实际的指数大小。

数值的计算:





特殊数值的表示:









gradual underflow——>the subnormal numbers

[Gradual underlow provides a number of advantages over abrupt underfow.Without it, the gap between zero and the smallest foating-point number is much larger than the gap between successive small foating-point numbers. Without gradual underfow one can find two values, X and Y (such that X is not equal to Y), and yet when you subtract them their result is zero. While a skilled numerical analyst could work around this limitation in many situations, this anomaly would tend to cause problems for less skilled programmers.——Charles Severance]



结论:小数部分最高有效位由指数部分决定。如果指数在0 < exponent < 2^e-1之间,那么小数部分最高有效位将是1,而且这个数将被称为正规形式。

如果指数是0,有效数最高有效位将会是0,并且这个数将被称为非正规形式。这里有三个特殊值需要指出:

如果 指数 是0 并且 小数部分 是0,这个数±0(和符号位相关)

如果 指数 = 2^e - 1 并且 小数部分 是0,这个数是 ±无穷大(同样和符号位相关)

如果 指数 = 2^e - 1 并且 小数部分 非0,这个数表示为不是一个数(NaN)。

以上规则,总结如下:

形势 指数 小数部分

零 0 0

非正规形式 0 非0

正规形式 1 到 2^e - 2 任意

无穷 2^e-1 0

NaN 2^e-1 非零



Fraction位二进制数所能表示的二进制个数是在2^(Fraction)个,而Fraction位十进制数可以表示的个数是10^(Fraction)个。

可以表示的比例是2^(Fraction)/10^(Fraction)=0.2^(Fraction)。Fraction越大,所能表示的浮点数的比例就越小。



Subnormal numbers:

The numbers closest to the inverse of these bounds (−1×10−95 and 1×10−95) are considered to be the smallest (in magnitude) normal numbers; non-zero numbers between these smallest numbers are called subnormal numbers.

Subnormal numbers provide the guarantee that addition and subtraction of floating-point numbers never underflows; two nearby floating-point numbers always have a representable non-zero difference. Without gradual underflow, the subtraction a−b can underflow and produce zero even though the values are not equal. This can, in turn, lead to division by zero errors that cannot occur when gradual underflow is used.

By filling the underflow gap like this, significant digits are lost, but not to the extent as when doing flush to zero on underflow (losing all significant digits all through the underflow gap). Hence the production of a denormal number is sometimes called gradual underflow because it allows a calculation to lose precision slowly when the result is small.



Some processors handle subnormal values in hardware, just as normal values are. Subnormal values (as arguments or results) then pose no particular performance issue; they are handled at the same speed as normal values. But some processors leave the handling of subnormal values to system software, only handling normal values (and zero) in hardware. In this case, computing with subnormal values is significantly slower than computing with normal values.Some applications need to contain code to avoid subnormal numbers. Either to maintain accuracy, or in order to avoid the performance penalty in some processors

If the exponent is all 0s, but the fraction is non-zero (else it would be interpreted as zero), then the value is a subnormalized number, which does not have an assumed leading 1 before the binary point. Thus, this represents a number (-1)s × 0.f × 2-126, where s is the sign bit and f is the fraction. For double precision, denormalized numbers are of the form (-1)s × 0.f × 2-1022. From this you can interpret zero as a special type of denormalized number.



各种类型数值计算中Subnormal的值:









3、浮点数的舍入

任何有效数上的运算结果,通常都存放在较长的寄存器中,当结果返回为浮点格式时,必须将多出来的位元丢弃。

有多种方法可以用来执行舍入作业,实际上IEEE标准列出4种不同的方法:

舍入到最接近:会将结果舍入为最接近且可以表示的值。

向+∞方向舍入:会将结果向正无限大的方向舍入。ceil()方法

向-∞方向舍入: 会将结果向负无限大的方向舍入。 floor()方法

向0方向舍入: 会将结果向0的方向舍入。 (int)截断舍入



IEEE754-2008的舍入算法:

1)Rounding to nearest(向最近的数值进行舍入)

Round to nearest,ties to eve向偶数进行方向舍入,也就是将最后一位取0的一种舍入方式;是默认的舍入方式,也是推荐的舍入方式。(理解:0是偶数,所以偶数比奇数多,自然取偶数的精确性更大些)

Round to nearest,ties away from zero 向远离0的一侧进行舍入;正数取大的数值,负数取小的数值。

2)Directed roundings(定向的舍入)

Round toward 0 向0方向舍入。

Round toward +∞ 将结果向正无限大的方向舍入。

Round toward -∞ 将结果向负无限大的方向舍入。







4、数值处理中的异常



标准定义了五种异常(非法操作、0除、上溢、下溢、不精确)

The standard defines five exceptions, each of which has a corresponding status flag that (except in certain cases of underflow) is raised when the exception occurs. No other action is required, but alternatives are recommended (see below).

The five possible exceptions are:

Invalid operation (e.g., square root of a negative number)

Division by zero

Overflow (a result is too large to be represented correctly)

Underflow (a result is very small (outside the normal range) and is inexact)

Inexact.



Underflow

Recall that the IEEE format for a normal floating-point number is:

(-1)

s..(e- bias) . (2...) . 1.f

where s is the sign bit, e is the biased exponent, and f is the fraction. Only s, e, and f need to be stored to fully specify the number. Because the implicit leading bit of the significand is defined to be 1 for normal numbers, it need not be stored.

The smallest positive normal number that can be stored, then, has the negative exponent of greatest magnitude and a fraction of all zeros. Even smaller numbers can be accommodated by considering the leading bit to be zero rather than one. In the double-precision format, this effectively extends the minimum exponent from 10-308 to 10-324, because the fraction part is 52 bits long (roughly 16 decimal digits.) These are the subnormal numbers; returning a subnormal number (rather than flushing an underflowed result to zero) is gradual underflow.

Clearly, the smaller a subnormal number, the fewer nonzero bits in its fraction; computations producing subnormal results do not enjoy the same bounds on relative roundoff error as computations on normal operands. However, the key fact about gradual underflow is that its use implies:

Underflowed results need never suffer a loss of accuracy any greater than that which results from ordinary roundoff error.

Addition, subtraction, comparison, and remainder are always exact when the result is very small.



Recall that the IEEE format for a subnormal floating-point number is:

(-1)

s..(- bias+ 1) . (2....) . 0.f

where s is the sign bit, the biased exponent e is zero, and f is the fraction. Note that the implicit power-of-two bias is one greater than the bias in the normal format, and the implicit leading bit of the fraction is zero.

Gradual underflow allows you to extend the lower range of representable numbers. It is not smallness that renders a value questionable, but its associated error. Algorithms exploiting subnormal numbers have smaller error bounds than other systems. The next section provides some mathematical justification for gradual underflow.



Why Gradual Underflow?

The purpose of subnormal numbers is not to avoid underflow/overflow entirely, as some other arithmetic models do. Rather, subnormal numbers eliminate underflow as a cause for concern for a variety of computations (typically, multiply followed by add). For a more detailed discussion, see "Underflow and the Reliability of Numerical Software" by James Demmel and "Combatting the Effects of Underflow and Overflow in Determining Real Roots of Polynomials" by S. Linnainmaa.



The presence of subnormal numbers in the arithmetic means that untrapped underflow (which implies loss of accuracy) cannot occur on addition or subtraction. If x and y are within a factor of two, then x -y is error-free. This is critical to a number of algorithms that effectively increase the working precision at critical places in algorithms.In addition, gradual underflow means that errors due to underflow are no worse than usual roundoff error. This is a much stronger statement than can be made about any other method of handling underflow, and this fact is one of the best justifications for gradual underflow.

Most of the time, floating-point results are rounded:

computed result = (true result). Roundoff

In IEEE arithmetic, with rounding mode to nearest,

1/2 ulp0 . roundoff .

of the computed result.ulp is an acronym for Unit in the Last Place. The least significant bit of the fraction of a number in its standard representation, is the last place. If the roundoff error is less than or equal to one half unit in the last place, then the calculation is correctly rounded.



the ulp for each floating point data type would be

Precision Value

single = 2^-23 ~ 1.192092896e-07

double = 2^-52 ~ 2.22044604925031308e-16

Intel double extended = 2^-11 ~ 1.92592994438723585305597794258492732e-34



Any conventional set of representable floating-point numbers has the property that the worst effect of one inexact result is to introduce an error no worse than the distance to one of the representable neighbors of the computed result. When subnormal numbers are added to the representable set and gradual underflow is implemented, the worst effect of one inexact or underflowed result is to introduce an error no greater than the distance to one of the representable neighbors of the computed result.

In particular, in the region between zero and the smallest normal number, the distance between any two neighboring numbers equals the distance between zero and the smallest subnormal number. The presence of subnormal numbers eliminates the possibility of introducing a roundoff error that is greater than the distance to the nearest representable number.

In the absence of gradual underflow, user programs need to be sensitive to the implicit inaccuracy threshold. For example, in single precision, if underflow occurs in some parts of a calculation, and Store 0 is used to replace underflowed results with 0, then accuracy can be guaranteed only to around 10-31, not 10-38, the usual lower range for single-precision exponents.

This means that programmers need to implement their own method of detecting when they are approaching this inaccuracy threshold, or else abandon the quest for a robust, stable implementation of their algorithm.Some algorithms can be scaled so that computations don't take place in the onstricted area near zero. However, scaling the algorithm and detecting the inaccuracy threshold can be difficult and time-consuming for each numerical program.



认识:

Gradual underflow可以使程序在截断数据的时间向更精确做出判断,提高数据精确度,subnormal number就是这种用来做判断的数据的一个范围值。







5、Java中的float和double

1)float

Float中,指数8位,小数位23位

指数范围:0~255(-127偏差)=-127~128

其中-127和128是用来表示特殊数字的-126~127表示的是正常数字。



以上数值为了表示0:因此用指数-127来表示:

0x00000000=(0,-127,1.0)=2^(-127)表示零。

以上数值为了表示无穷和非数:因此用指数128来表示:

0x[7
f]f[8
c]......=(0
1,128,)=2^128当小数部分是0时表示无穷大;当小数部分不是0时表示的是非数。

因此,有效范围内的最大正值为0x7f7fffff(0,127,7ffff)

有效范围内的最小正值为0x00000001=2^(-23)*2^(-126)=2^(-149)

MIN_NORMAL=0x0080000=(0,1,0)=2^(-126)

Why -126?

Otherwise we’d be skipping numbers

0.1 * 2-126 = 1.0 * 2-127



Subnormal number为最小值到MIN_NORMAL之间的所有数值。



Subnormalized Normalized Approximate Decimal

Single Precision ± 2-149 to (1-2-23)×2-126

= (2-23——1-2-23)×2-126 ± 2-126 to (2-2-23)×2127 ± ~10-44.85 to ~1038.53

Double Precision ± 2-1074 to (1-2-52)×2-1022

=(2-52——1-2-52)×2-1022 ± 2-1022 to (2-2-52)×21023 ± ~10-323.3 to ~10308.3





java.lang.Float中

//@code Float.intBitsToFloat(0x7f800000)即(0,255-127=128,)

public static final double POSITIVE_INFINITY = 1.0 / 0.0;

//@Float.intBitsToFloat(0xff800000)即(0,255-127=128,)

public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;

//@Float.intBitsToFloat(0x7fc00000)即(0,255-127=128,)

public static final float NaN = 0.0f / 0.0f;

//@Float.intBitsToFloat(0x7f7fffff).

public static final float MAX_VALUE = 0x1.fffffeP+127f; // 3.4028235e+38f

//@code Float.intBitsToFloat(0x00800000即(0,1-127=-126,)

public static final float MIN_NORMAL = 0x1.0p-126f; // 1.17549435E-38f

//@code Float.intBitsToFloat(0x1) 即(0,0-127=-127,)

public static final float MIN_VALUE = 0x0.000002P-126f; // 1.4e-45f



获取代表float的32bit的int型表示:

public static int floatToIntBits(float value)

public static native int floatToRawIntBits(float value)

返回32bit代表的float浮点数值:

public static native float intBitsToFloat(int bits)





2)double

java.lang.Double中

//@code Double.longBitsToDouble(0x7ff0000000000000L).

public static final double POSITIVE_INFINITY = 1.0 / 0.0;

//@code Double.longBitsToDouble(0xfff0000000000000L).

public static final double NEGATIVE_INFINITY = -1.0 / 0.0;

//@code Double.longBitsToDouble(0x7ff8000000000000L).

public static final double NaN = 0.0d / 0.0;

//@code Double.longBitsToDouble(0x7fefffffffffffffL).

public static final double MAX_VALUE = 0x1.fffffffffffffP+1023; // 1.7976931348623157e+308

//@code Double.longBitsToDouble(0x0010000000000000L)

public static final double MIN_NORMAL = 0x1.0p-1022; // 2.2250738585072014E-308

//@code Double.longBitsToDouble(0x1L)

public static final double MIN_VALUE = 0x0.0000000000001P-1022; // 4.9e-324



获取代表double的64bit的long型表示:

public static long doubleToLongBits(double value)

public static native long doubleToRawLongBits(double value)

返回64bit代表的double浮点数值:

public static native double longBitsToDouble(long bits)









6、Java中的BigDecimal

extends Number implements Comparable

实现了比较接口,可以进行相互之间的比较。

不可变的、任意精度的有符号十进制数。BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。因此,BigDecimal 表示的数值是 (unscaledValue × 10-scale)。





在金融以及涉及到钱的计算中,都需要使用该类替换double,以防止引起累积误差,获取高准确的数值计算。

测试代码:

double v1 = 1.0;

double v2 = 0.9;

out.println(v1 - v2);



BigDecimal value1 = new BigDecimal(Double.toString(v1));

BigDecimal value2 = new BigDecimal(Double.toString(v2));

out.println(value1.subtract(value2));





使用时的注意:

1)构造函数采用String参数而不是double参数,因为double val本身就是一个精确表示的值。

public BigDecimal(String val)

2)基本运算



public BigDecimal add(BigDecimal augend)



public BigDecimal subtract(BigDecimal subtrahend)



public BigDecimal multiply(BigDecimal multiplicand)



public BigDecimal divide(BigDecimal divisor)

3)舍入方式

// Rounding Modes



//Rounding mode to round away from zero. Always increments the digit.

public final static int ROUND_UP = 0;



//Rounding mode to round towards zero. Never increments the digit.

public final static int ROUND_DOWN = 1;





//Rounding mode to round towards positive infinity.

public final static int ROUND_CEILING = 2;



//Rounding mode to round towards negative infinity.

public final static int ROUND_FLOOR = 3;



//Rounding mode to round towards nearest neighbor

//unless both neighbors are equidistant, in which case round up.

public final static int ROUND_HALF_UP = 4;



//Rounding mode to round towards nearest neighbor

//unless both neighbors are equidistant, in which case round down.

public final static int ROUND_HALF_DOWN = 5;



//Rounding mode to round towards the nearest neighbor

//unless both neighbors are equidistant, in which case, round

// towards the even neighbor.

public final static int ROUND_HALF_EVEN = 6;



//Rounding mode to assert that the requested operation has an exact

// result, hence no rounding is necessary.

public final static int ROUND_UNNECESSARY = 7;



4)比较两个数值大小的方法是:

public int compareTo(BigDecimal val)

不能用

public boolean equals(Object x)

进行。

测试代码:

import java.math.BigDecimal;

import static java.lang.System.out;



BigDecimal value11 = new BigDecimal("1");

BigDecimal value21 = new BigDecimal("1.0");

out.println(value11.equals(value21));//false

out.println(value11.compareTo(value21) == 0 ? true : false);//true









7、IEEE-754发展

IEEE 754-2008 governs binary floating-point arithmetic. It specifies number formats, basic operations, conversions, and exceptional conditions.The 2008 edition supersedes both the 754-1985 standard and the related IEEE 854-1987 which generalized 754-1985 to cover decimal arithmetic as well as binary.

IEEE-7442008标准定义了:

1)arithmetic formats:二进制、十进制浮点数;

signed zeros,subormal numbers,infinites,NaN(Not a Number) ;

2)interchange formats:encoding(bit strings)编码数据在交换时已获得更高效率;

3)rounding algorithms:在计算和转换时进行的舍入方式;

4)operations:操作符在计算层次上的格式;

5)exception handling:指示异常条件(0除、溢出)。



Basic Format(基本格式):

Name Common name Base Digits E min E max Notes

binary16

Half precision 2 10+1 -14 +15 storage, not basic

binary32

Single precision 2 23+1 -126 +127

binary64

Double precision 2 52+1 -1022 +1023

binary128

Quadruple precision 2 112+1 -16382 +16383

decimal32

10 7 -95 +96 storage, not basic

decimal64

10 16 -383 +384

decimal128

10 34 -6143 +6144

All the basic formats are available in both hardware and software implementations



Arithmetic Format(算术格式):

用浮点数的sign(符号位)、significand(小数位)、exponent(指数位)表示的浮点数。



Interchange format(交换格式)

The width of the exponent field for a k-bit format is computed as

w = round(4×log2(k))−13.(指数位位数的计算公式)





十进制浮点数:

Kahan教授的看法:使用十进制浮点数,以避免人为错误。也就是这种错误:double d = 0.1;实际上,d≠0.1。IBM公司的看法:在经济、金融和与人相关的程序中,使用十进制浮点数。但是,由于没有硬件支持,用软件实现的十进制浮点计算比硬件实现的二进制浮点计算要慢100-1000倍。由于被IEEE 754R所采纳,IBM公司将在下一代Power芯片中实现十进制FPU。





总结附录图表:









Reference:

EN

http://standards.ieee.org/

http://ieeexplore.ieee.org/servlet/opac?punumber=2355

http://ieeexplore.ieee.org/servlet/opac?punumber=2502

http://ieeexplore.ieee.org/servlet/opac?punumber=4610933

http://www.cs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF

http://babbage.cs.qc.edu/courses/cs341/IEEE-754references.html

http://grouper.ieee.org/groups/754/meeting-materials/2001-10-18-langdesign.pdf

http://steve.hollasch.net/cgindex/coding/ieeefloat.html

http://754r.ucbtest.org/

http://grouper.ieee.org/groups/754/

http://en.wikipedia.org/wiki/IEEE_754-2008

http://en.wikipedia.org/wiki/Subnormal_number

http://docs.sun.com/source/806-3568/ncgTOC.html

http://docs.sun.com/source/806-3568/ncg_goldberg.html

http://chrishecker.com/Miscellaneous_Technical_Articles#Floating_Point

http://chrishecker.com/Miscellaneous_Technical_Articles

http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.2.3

http://babbage.cs.qc.edu/IEEE-754/IEEE-754hex32.html

http://hal.archives-ouvertes.fr/hal-00128124/en/

http://www.dec.usc.es/arith16/docs/ieee_toc_arith_cfp.pdf

http://docs.sun.com/app/docs/doc/802-5692/6i9ecs3oa?l=zh&a=view



ZH

http://zh.wikipedia.org/wiki/%E6%B5%AE%E7%82%B9%E6%95%B0

http://zh.wikipedia.org/wiki/IEEE_754

http://codex.wordpress.org.cn/IEEE_754

http://www.cnblogs.com/bossin/archive/2007/04/08/704567.html

http://stephensuen.spaces.live.com/Blog/cns!1p1G_DGhjYiYGmj6keNZQAcw!172.entry

http://docs.sun.com/app/docs/coll/44.4?l=zh

Java中编码以及Unicode总结

1、基本概念


bit 位 只能是0或者1

byte 字节 一个字节是8位,1 byte=8 bits 计算机表示的基本单位

KB,MB,GB,TB,PB是以1024与byte进行换算

进制 用符号进行计数 十进制、二进制、八进制(011)、十六进制(0xFF)

字符 文字和符号的总称

字符集 多个字符集合的总称。ASCII字符集、GB2312字符集、GBK字符集、BIG5字符集、GB18003字符集、Unicode字符集



byte可表示2^8=256个字符的表示

0 0x00 0000,0000

1 0x01 0000,0001

2 0x01 0000,0010

127 0x7F 0111,1111

-128 0x80 1000,0000

-2 0xFE 1111,1110

-1 0xFF 1111,1111

以补码的形式表示的二进制编码。

-2的表示,2=0000,0010,反码1111,1101,补码=反码+1=11111110

1111,1110表示的就是1111,1110-1=1111,1101,取反就是0000,0010也就是2,所以就是-2



2、字符集和编码

2.1字符(Character)

字符(Character)是文字与符号的总称,包括文字、图形符号、数学符号等。

2.2字符集(Character Set)

一组抽象字符的集合就是字符集(Character Set)。字符集常常和一种具体的语言文字对应起来,该文字中的所有字符或者大部分常用字符就构成了该文字的字符集,比如英文字符集。一组有共同特征的字符也可以组成字符集,比如繁体汉字字符集、日文汉字字符集。字符集的子集也是字符集。

计算机要处理各种字符,就需要将字符和二进制内码对应起来,这种对应关系就是字符编码(Encoding)。制定编码首先要确定字符集,并将字符集内的字符排序,然后和二进制数字对应起来。根据字符集内字符的多少,会确定用几个字节来编码。每种编码都限定了一个明确的字符集合,叫做被编码过的字符集(Coded Character Set),这是字符集的另外一个含义。通常所说的字符集大多都是指编码字符集(Coded Character Set)。



2.2.1 ASCII字符集

ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)是基于罗马字母表的一套电脑编码系统。由美国国家标准局(ANSI)制定。

7位,可以表示2^7=128个字符。在计算机的存储单元中,一个ASCII码值占一个字节(8个二进制位),其最高位(b7)用作奇偶校验位。 7位编码的字符集只能支持128个字符,为了表示更多的欧洲常用字符对ASCII进行了扩展,ASCII扩展字符集使用8位(bits)表示一个字符,共256字符。

ASCII扩展字符集比ASCII字符集扩充出来的符号包括表格符号、计算符号、希腊字母和特殊的拉丁符号。



2.2.2 GB2312 字符集

GB2312又称为GB2312-80字符集,全称为《信息交换用汉字编码字符集•基本集》,由原中国国家标准总局发布,1981年5月1日实施。在中国大陆和新加坡获广泛使用。GB2312收录简化汉字及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。其中包括6763个汉字,其中一级汉字3755个,二级汉字3008个;包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。

GB2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码。各区包含的字符如下:01-09区为特殊符号;16-55区为一级汉字,按拼音排序;56-87区为二级汉字,按部首/笔画排序;10-15区及88-94区则未有编码。

两个字节中前面的字节为第一字节,后面的字节为第二字节。习惯上称第一字节为“高字节” ,而称第二字节为“低字节”。“高位字节”使用了0xA1-0xF7 (把01-87区(88-94区未有编码)的区号加上0xA0),“低位字节”使用了0xA1-0xFE (把01-94加上0xA0)。

以GB2312字符集的第一个汉字“啊”字为例,它的区号16,位号01,则区位码是1601,在大多数计算机程序中,高字节和低字节分别加0xA0得到程序的汉字处理编码0xB0A1。计算公式是:0xB0=0xA0+16, 0xA1=0xA0+1。



2.2.3 GBK 字符集

GBK全名为汉字内码扩展规范,英文名Chinese Internal Code Specification。K 即是“扩展”所对应的汉语拼音(KuoZhan11)中“扩”字的声母。GBK 来自中国国家标准代码GB 13000.1-93。GBK: 汉字国标扩展码,基本上采用了原来GB2312-80所有的汉字及码位,并涵盖了原Unicode中所有的汉字20902,总共收录了883个符号, 21003个汉字及提供了1894个造字码位。[(GBKH-0xB0)*0x5E+(GBKL-0xA1)]*(汉字离散后每个汉字点阵所占用的字节)

GBK是GB2312的扩展,是向上兼容的,因此GB2312中的汉字的编码与GBK中汉字的相同。另外,GBK中还包含繁体字的编码。

GBK中每个汉字仍然包含两个字节,第一个字节的范围是0x81-0xFE(即129-254),第二个字节的范围是0x40-0xFE(即64-254)。GBK中有码位23940个,包含汉字21003个。



2.2.4 BIG5字符集

又称大五码或五大码,1984年由台湾财团法人信息工业策进会和五间软件公司宏碁 (Acer)、神通 (MiTAC)、佳佳、零壹 (Zero One)、大众 (FIC)创立,故称大五码。Big5码的产生,是因为当时台湾不同厂商各自推出不同的编码,如倚天码、IBM PS55、王安码等,彼此不能兼容;另一方面,台湾政府当时尚未推出官方的汉字编码,而中国大陆的GB2312编码亦未有收录繁体中文字。

Big5字符集共收录13,053个中文字,该字符集在中国台湾使用。耐人寻味的是该字符集重复地收录了两个相同的字:“兀”(0xA461及0xC94A)、“嗀”(0xDCD1及0xDDFC)。

Big5码使用了双字节储存方法,以两个字节来编码一个字。第一个字节称为“高位字节”,第二个字节称为“低位字节”。高位字节的编码范围0xA1-0xF9,低位字节的编码范围0x40-0x7E及0xA1-0xFE。各编码范围对应的字符类型如下:0xA140-0xA3BF为标点符号、希腊字母及特殊符号,另外于0xA259-0xA261,存放了双音节度量衡单位用字:兙兛兞兝兡兣嗧瓩糎;0xA440-0xC67E为常用汉字,先按笔划再按部首排序;0xC940-0xF9D5为次常用汉字,亦是先按笔划再按部首排序。



2.2.5 GB18030字符集

GB 18030的全称是GB18030-2000《信息交换用汉字编码字符集基本集的扩充》,是我国政府于2000年3月17日发布的新的汉字编码国家标准,2001年8月31日后在中国市场上发布的软件必须符合本标准。GB 18030-2000收录了27533个汉字,GB 18030-2005收录了70244个汉字。GB18030的总编码空间超过150万个码位。

GB 18030字符集标准解决汉字、日文假名、朝鲜语和中国少数民族文字组成的大字符集计算机编码问题。该标准的字符总编码空间超过150万个编码位,收录了 27484个汉字,覆盖中文、日文、朝鲜语和中国少数民族文字。满足中国大陆、香港、台湾、日本和韩国等东亚地区信息交换多文种、大字量、多用途、统一编码格式的要求。并且与Unicode 3.0版本兼容,填补Unicode扩展字符字汇“统一汉字扩展A”的内容。并且与以前的国家字符编码标准(GB2312,GB13000.1)兼容。

GB 18030标准采用单字节、双字节和四字节三种方式对字符编码。单字节部分使用0×00至0×7F码(对应于ASCII码的相应码)。双字节部分,首字节码从0×81至0×FE,尾字节码位分别是0×40至0×7E和0×80至0×FE。四字节部分采用GB/T 11383未采用的0×30到0×39作为对双字节编码扩充的后缀,这样扩充的四字节编码,其范围为0×81308130到0×FE39FE39。其中第一、三个字节编码码位均为0×81至0×FE,第二、四个字节编码码位均为0×30至0×39。

双字节部分收录内容主要包括GB13000.1全部CJK汉字20902个、有关标点符号、表意文字描述符13个、增补的汉字和部首/构件80个、双字节编码的欧元符号等。四字节部分收录了上述双字节字符之外的,包括CJK统一汉字扩充A在内的GB 13000.1中的全部字符。



2.2.6ANSI编码

不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。"DBCS"(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。



汉字编码范围

名称 第一字节 第二字节

GB2312 0xB0-0xF7(176-247) 0xA0-0xFE(160-254)

GBK 0x81-0xFE(129-254) 0x40-0xFE(64-254)

Big5 0x81-0xFE(129-255) 0x40-0x7E(64-126)或者0xA1-0xFE(161-254)



2.3 字符集编码(Character Set Encoding)

ASCII,GB2312,GBK,BIG5,GB18030, UCS,Utf-8,utf-16,utf-32 都有自己不同的规则,都有自己的对应规则,但都兼容ASCII。在使用时要注意这些编码相互之间的转换规则。对于没有转换规则的编码体系之间进行转换只能依靠查编码表进行。



2.4 ISO的编码体系

2.4.1 ASCII编码

ASCII的编号是ISO-646。



2.4.2 ISO8859编码

ISO 8859,全称ISO/IEC 8859,是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准,现时定义了17个字符集。

* ISO 8859-1 (Latin-1) - 西欧语言

* ISO 8859-2 (Latin-2) - 中欧语言

* ISO 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。

* ISO 8859-4 (Latin-4) - 北欧语言

* ISO 8859-5 (Cyrillic) - 斯拉夫语言

* ISO 8859-6 (Arabic) - 阿拉伯语

* ISO 8859-7 (Greek) - 希腊语

* ISO 8859-8 (Hebrew) - 希伯来语(视觉顺序)

* ISO 8859-8-I - 希伯来语(逻辑顺序)

* ISO 8859-9 (Latin-5 或 Turkish) - 它把Latin-1的冰岛语字母换走,加入土耳其语字母。

* ISO 8859-10 (Latin-6 或 Nordic) - 北日耳曼语支,用来代替Latin-4。

* ISO 8859-11 (Thai) - 泰语,从泰国的 TIS620 标准字集演化而来。

* ISO 8859-13 (Latin-7 或 Baltic Rim) - 波罗的语族

* ISO 8859-14 (Latin-8 或 Celtic) - 凯尔特语族

* ISO 8859-15 (Latin-9) - 西欧语言,加入Latin-1欠缺的法语及芬兰语重音字母,以及欧元符号。

* ISO 8859-16 (Latin-10) - 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号。



2.4.3ISO10046(UCS)编码与Unicode

UCS :

通用字符集(Universal Character Set,UCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的字符编码方式,采用4字节编码。



Unicode:

Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。

它是http://www.unicode.org 制定的编码机制, 要将全世界常用文字都函括进去。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。但自从unicode2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码,ISO也承诺ISO10646将不会给超出0x10FFFF的UCS-4编码赋值,使得两者保持一致。Unicode的编码方式与ISO 10646的通用字符集(Universal Character Set,UCS)概念相对应,目前的用于实用的Unicode版本对应于UCS-2,使用16位的编码空间。也就是每个字符占用2个字节,基本满足各种语言的使用。实际上目前版本的Unicode尚未填充满这16位编码,保留了大量空间作为特殊使用或将来扩展。

UTF:

Unicode 的实现方式不同于编码方式。

一个字符的Unicode编码是确定的,但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Translation Format,简称为 UTF)。

UTF-8: 8bit变长编码,对于大多数常用字符集(ASCII中0~127字符)它只使用单字节,而对其它常用字符(特别是朝鲜和汉语会意文字),它使用3字节。

UTF-16: 16bit编码,是变长码,大致相当于20位编码,值在0x0000到

0x10FFFF之间,基本上就是Unicode编码的实现,与CPU字序有关。

UTF-32:32bit编码,定长编码对应于字符的Unicode表示。



Unicode big endia:

在Windows系统中保存文本文件时通常可以选择编码为ANSI、Unicode、Unicode big endian和UTF-8,这里的ANSI和Unicode big endia是什么编码呢?

UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。

Unicode规范中推荐的标记字节顺序的方法是BOM(即Byte Order Mark)。

在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。

如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。Windows就是使用BOM来标记文本文件的编码方式的。



2.5 codepage的编码体系

codepage 指的是一个经过挑选的以特定顺序排列的字符内码列表,对于早期的单字节内码的语种,codepage中的内码顺序使得系统可以按照此列表来根据键盘的输入值给出一个对应的内码.对于双字节内码,则给出的是MultiByte到Unicode的对应表,这样就可以把以Unicode形式存放的字符转化为相应的字符内码.类似unicode,只是另外一种字符编码方式,注意ASP和SAP中的codepage的区别。

  ASP中:

  CodePage的作用,是决定页面以何种编码方式显示动态内容。当页面被服务器处理之后,页面将以CodePage设定的编码输出到客户端。当然,CodePage的参数需正确,否则,将产生错误信息“CodePage 值无效。指定的 CodePage 值无效。”(事件ID: 0204)。如果CodePage没有设置,则服务器使用默认的CodePage加载到你的Session里面,使用程序代码: Response.Write(Session.CodePage)可以查看你当前使用的CodePage。

  SAP中:最经常我们使用的读取数据的方法就是使用GUI_UPLOAD这个FM.在这个FM中有个CODEPAGE,是用来指定代码页的.

  

Siebel Value

   SAP Code page

   Description  

CP1252

   1100

   SAP Latin-1 - ISO8859-1 - code page  

ISO-8859-2

   1402

   SAP Latin-2 - ISO8859-2  

ISO-8859-5

   1500

   SAP Cyrillic - ISO8859-5  

CP1254

   1610

   SAP Turkish - ISO8859-9  

CP1253

   1700

   SAP Greek - ISO8859-7 - Not a complete match  

CP1255

   1800

   SAP Hebrew - ISO8859-8 - Not a complete match  

CP932

   8000

   SAP Shift-JIS  

CP950

   8300

   SAP Taiwanese  

CP936

   8400

   SAP Chinese

CP949

   8500

   SAP Korean  

CP874

   8600

   SAP Thai



3、Unicode历史

1991年,Unicode联盟与ISO的工作组终于开始讨论Unicode与UCS的合并问题。最终,两者统一了抽象字符集(即任何一个在Unicode中存在的字符,在UCS中也存在),对于码空间,两者同意以一百一十万为限,Unicode将码空间扩展到了一百一十万,而UCS将永久性的不使用一百一十万以后的码位。UCS和Unicode都指的是编码字符集,而不是字符集编码。

字符集编码决定了如何将一个字符的整数编号对应到一个二进制的整数值,有的编码方案简单的将该整数值直接作为其在计算机中的表示而存储,例如英文字符就是这样,几乎所有的字符集编码方案中,英文字母的整数编号与其在计算机内部存储的二进制形式都一致。当初Unicode与UCS还没成家之时,UCS也是需要人爱,需要人疼的,没有自己的字符集编码怎么成。UCS-2与UCS-4就扮演了这样的角色。 UCS-4与UTF-32除了名字不同以外,思想完全一样。而UCS-2与UTF-16在对前65536个字符的处理上也完全相同,唯一的区别只在于 UCS-2 不支持surrogate pair机制,即是说,UCS-2只能对前65536个字符编码,对其后的字符毫无办法。



4、Unicode的编码形式

4.1 Unicode字符集

Unicode字符集编码是Universal Multiple-Octet Coded Character Set 通用多八位编码字符集的简称,是由一个名为 Unicode 学术学会(Unicode Consortium)的机构制订的字符

编码系统,支持现今世界各种不同语言的书面文本的交换、处理及显示。该编码于1990年开始研发,1994年正式公布,最新版本是2005年3月31日的Unicode 4.1.0。Unicode是一种在计算机上使用的字符编码。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。Unicode 标准始终使用十六进制数字,而且在书写时在前面加上前缀“U+”,例如字母“A”的编码为 004116,所以“A”的编码书写为“U+0041”。



现在的Unicode码空间为U+0000到U+10FFFF,一共1114112个码位,其中只有1,112,064 个码位是合法的(我来替你做算术,有2048个码位不合法),但并不是说现在的Unicode就有这么多个字符了,实际上其中很多码位还是空闲的,到 Unicode 4.0 规范为止,只有96,382个码位被分配了字符(但无论如何,仍比很多人认为的65536个字符要多得多了)。其中U+0000 到U+FFFF的部分被称为基本多语言面(Basic Multilingual Plane,BMP)。U+10000及以上的字符称为补充字符。在Java中(Java1.5之后),补充字符使用两个char型变量来表示,这两个 char型变量就组成了所谓的surrogate pair(在底层实际上是使用一个int进行表示的)。第一个char型变量的范围称为“高代理部分”(high-surrogates range,从"uD800到"uDBFF,共1024个码位), 第二个char型变量的范围称为low-surrogates range(从"uDC00到"uDFFF,共1024个码位),这样使用surrogate pair可以表示的字符数一共是1024的平方计1048576个,加上BMP的65536个码位,去掉2048个非法的码位,正好是1,112,064 个码位。

关于Unicode的码空间实际上有一些稍不小心就会让人犯错的地方。比如我们都知道从U+0000到U+FFFF的部分被称为基本多语言面(Basic Multilingual Plane,BMP),这个范围内的字符在使用UTF-16编码时,只需要一个char型变量就可以保存。仔细看看这个范围,应该有65536这么大,因此你会说单字节的UTF-16编码能够表示65536个字符,你也会说Unicode的基本多语言面包含65536个字符,但是再想想刚才说过的 surrogate pair,一个UTF-16表示的增补字符(再一次的,需要两个char型变量才能表示的字符)怎样才能被正确的识别为增补字符,而不是两个普通的字符呢?答案你也知道,就是通过看它的第一个char是不是在高代理范围内,第二个char是不是在低代理范围内来决定,这也意味着,高代理和低代理所占的共 2048个码位(从0xD800到0xDFFF)是不能分配给其他字符的。但这是对UTF-16这种编码方法而言,而对Unicode这样的字符集呢?在Unicode的编号中,U+D800到U+DFFF是否有字符分配?答案是也没有!这是典型的字符集为方便编码方法而做的安排(你问他们这么做的目的?当然是希望基本多语言面中的字符和一个char型的UTF-16编码的字符能够一一对应,少些麻烦,从中我们也能看出UTF-16与Unicode间很深的渊源与结合)。也就是说,无论Unicode还是UTF-16编码后的字符,在0x0000至0xFFFF这个范围内,只有63488个字符。这就好比最初的CPU被勉强拿来做多媒体应用,用得多了,CPU就不得不修正自己从硬件上对多媒体应用提供支持了。

尽管不情愿,但说到这里总还得扯扯相关的概念:代码点和代码单元。代码点(Code Point)就是指Unicode中为字符分配的编号,一个字符只占一个代码点,例如我们说到字符“汉”,它的代码点是U+6C49.代码单元(Code Unit)则是针对编码方法而言,它指的是编码方法中对一个字符编码以后所占的最小存储单元。例如UTF-8中,代码单元是一个字节,因为一个字符可以被编码为1个,2个或者3个4个字节;在UTF-16中,代码单元变成了两个字节(就是一个 char),因为一个字符可以被编码为1个或2个char(你找不到比一个char还小的UTF-16编码的字符,嘿嘿)。说得再罗嗦一点,一个字符,仅仅对应一个代码点,但却可能有多个代码单元(即可能被编码为2个char)。 以上概念绝非学术化的绕口令,这意味着当你想以一种统一的方式指定自己使用什么字符的时候,使用代码点(即你告诉你的程序,你要用Unicode中的第几个字符)总是比使用代码单元更好(因为这样做的话你还得区分情况,有时候提供一个16进制数字,有时候要提供两个)。

例如我们有一个增补字符???(哈哈,你看到了三个问号对吧?因为我的系统显示不出这个字符),它在Unicode中的编号是U+2F81A,当在程序中需要使用这个字符的时候,就可以这样来写:

String s=String.valueOf(Character.toChars(0x2F81A));

char[]chars=s.toCharArray();

for(char c:chars){

System.out.format("%x",(short)c);

}

后面的for循环把这个字符的UTF-16编码打印了出来,结果是d87edc1a注意到了吗?这个字符变成了两个char型变量,其中0xd87e就是高代理部分的值,0xdc1a就是低代理的值。

Unicode字符集编码(Universal Multiple-Octet Coded Character Set)通用多八位编码字符集的简称,支持世界上超过650种语言的国际字符集。Unicode是一种在计算机上使用的字符编码。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。Unicode 标准始终使用十六进制数字,而且在书写时在前面加上前缀“U+”,字母“A”的编码书写为“U+0041”。

Unicode计划使用了17个平面,一共有 17*65536=1114112个码位。在Unicode 5.0.0版本中,已定义的码位只有238605个,分布在平面0、平面1、平面2、平面14、平面15、平面16。其中平面15和平面16上只是定义了两个各占65534个码位的专用区(Private Use Area),分别是0xF0000-0xFFFFD和0x100000-0x10FFFD。所谓专用区,就是保留给大家放自定义字符的区域,可以简写为 PUA。平面0也有一个专用区:0xE000-0xF8FF,有6400个码位。平面0的 0xD800-0xDFFF,共2048个码位,是一个被称作代理区(Surrogate)的特殊区域。代理区的目的用两个UTF-16字符表示BMP以外的字符。在介绍UTF-16编码时会介绍。如前所述在Unicode 5.0.0版本中,238605-65534*2-6400-2408=99089。余下的99089个已定义码位分布在平面0、平面1、平面2和平面 14上,它们对应着Unicode目前定义的99089个字符,其中包括71226个汉字。平面0、平面1、平面2和平面14上分别定义了52080、 3419、43253和337个字符。平面2的43253个字符都是汉字。平面0上定义了27973个汉字。



4.2 UCS编码

早期的Unicode标准有UCS-2、UCS-4的说法。UCS-2用两个字节编码,UCS-4用4个字节编码。UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个平面(plane)。每个平面根据第3个字节分为256行(row),每行有256个码位(cell)。group 0的平面0被称作BMP(Basic Multilingual Plane)。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。

每个平面有2^16=65536个码位。



4.3 UTF编码

Unicode 标准的编码字符集的字符编码方案。UTF是 Unicode Translation Format,即把Unicode转做某种格式的意思。UTF-32、UTF-16 和 UTF-8 是 Unicode 标准的编码字符集的字符编码方案,UTF-16 使用一个或两个未分配的16位代码单元的序列对Unicode 代码点进行编码;UTF-32 即将每一个 Unicode 代码点表示为相同值的 32 位整数。

在Unicode中:汉字“字”对应的数字是23383。在Unicode中,我们有很多方式将数字23383表示成程序中的数据,包括:UTF-8、 UTF-16、UTF-32。UTF是“UCS Transformation Format”的缩写,可以翻译成Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。例如,“汉字”对应的数字是 0x6c49和0x5b57,而编码的程序数据是:

  BYTE data_utf8[] = {0xE6, 0xB1, 0x89, 0xE5, 0xAD, 0x97}; // UTF-8编码

  WORD data_utf16[] = {0x6c49, 0x5b57}; // UTF-16编码

  DWORD data_utf32[] = {0x6c49, 0x5b57}; // UTF-32编码

这里用BYTE、WORD、DWORD分别表示无符号8位整数,无符号16位整数和无符号32 位整数。UTF-8、UTF-16、UTF-32分别以BYTE、WORD、DWORD作为编码单位。“汉字”的UTF-8编码需要6个字节。“汉字”的 UTF-16编码需要两个WORD,大小是4个字节。“汉字”的UTF-32编码需要两个DWORD,大小是8个字节。根据字节序的不同,UTF-16可以被实现为UTF-16LE或UTF-16BE,UTF-32可以被实现为UTF-32LE或UTF-32BE。下面介绍UTF-8、UTF-16、 UTF-32、字节序和BOM。



UTF-8

UTF-8最多是使用3个字节来表示一个字符。但理论上来说,UTF-8最多需要用6字节表示一个字符。

00000000-0000007F的字符,用单个字节来表示;

00000080-000007FF的字符用两个字节表示 (中文的编码范围)

00000800-0000FFFF的字符用3字节表示



UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:

  Unicode编码 ║ UTF-8 字节流(二进制)

  000000 - 00007F ║ 0xxxxxxx 7bit

  000080 - 0007FF ║ 110xxxxx 10xxxxxx 11bit

  000800 - 00FFFF ║ 1110xxxx 10xxxxxx 10xxxxxx 16bit

  010000 - 10FFFF ║ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 21bit

  UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。

  例1:“汉”字的Unicode编码是0x6C49。0x6C49在 0x0800-0xFFFF之间,使用用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

  例2:Unicode编码0x20C30在0x010000-0x10FFFF之间,使用用4 字节模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。

  

UTF-16

  UTF-16编码以16位无符号整数为单位。我们把Unicode编码记作U。编码规则如下:

  如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。

  如果U≥0x10000,我们先计算U'=U-0x10000,然后将U'写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

  为什么U'可以被写成20个二进制位?Unicode的最大码位是0x10ffff,减去 0x10000后,U'的最大值是0xfffff,所以肯定可以用20个二进制位表示。例如:Unicode编码0x20C30,减去0x10000后,得到0x10C30,写成二进制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的y,用后10位依次替代模板中的x,就得到:1101100001000011 1101110000110000,即0xD843 0xDC30。

  按照上述规则,Unicode编码0x10000-0x10FFFF的UTF-16编码有两个 WORD,第一个WORD的高6位是110110,第二个WORD的高6位是110111。可见,第一个WORD的取值范围(二进制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。第二个WORD的取值范围(二进制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。

  为了将一个WORD的UTF-16编码与两个WORD的UTF-16编码区分开来,Unicode编码的设计者将0xD800-0xDFFF保留下来,并称为代理区(Surrogate):

  D800-DB7F ║ High Surrogates ║ 高位替代

  DB80-DBFF ║ High Private Use Surrogates ║ 高位专用替代

  DC00-DFFF ║ Low Surrogates ║ 低位替代

  高位替代就是指这个范围的码位是两个WORD的UTF-16编码的第一个WORD。低位替代就是指这个范围的码位是两个WORD的UTF-16编码的第二个WORD。那么,高位专用替代是什么意思?我们来解答这个问题,顺便看看怎么由UTF-16 编码推导Unicode编码。

  如果一个字符的UTF-16编码的第一个WORD在0xDB80到0xDBFF之间,那么它的 Unicode编码在什么范围内?我们知道第二个WORD的取值范围是0xDC00-0xDFFF,所以这个字符的UTF-16编码范围应该是 0xDB80 0xDC00到0xDBFF 0xDFFF。我们将这个范围写成二进制:

1101101110000000 1101110000000000 - 1101101111111111 1101111111111111

  按照编码的相反步骤,取出高低WORD的后10位,并拼在一起,得到

  1110 0000 0000 0000 0000 - 1111 1111 1111 1111 1111

即0xe0000-0xfffff,按照编码的相反步骤再加上0x10000,得到 0xf0000-0x10ffff。这就是UTF-16编码的第一个WORD在0xdb80到0xdbff之间的Unicode编码范围,即平面15和平面16。因为Unicode标准将平面15和平面16都作为专用区,所以0xDB80到0xDBFF之间的保留码位被称作高位专用替代。



  UTF-32

  UTF-32编码以32位无符号整数为单位。Unicode的UTF-32编码就是其对应的32位无符号整数。

  

字节序

  根据字节序的不同,UTF-16可以被实现为UTF-16LE或UTF-16BE,UTF-32可以被实现为UTF-32LE或UTF-32BE。例如:

  Unicode编码 ║ UTF-16LE ║ UTF-16BE ║ UTF32-LE ║ UTF32-BE

  0x006C49 ║ 49 6C ║ 6C 49 ║ 49 6C 00 00 ║ 00 00 6C 49

0x020C30 ║ 43 D8 30 DC ║ D8 43 DC 30 ║ 30 0C 02 00 ║ 00 02 0C 30



BOM

  那么,怎么判断字节流的字节序呢?Unicode标准建议用BOM(Byte Order Mark)来区分字节序,即在传输字节流前,先传输被作为BOM的字符"零宽无中断空格"。这个字符的编码是FEFF,而反过来的FFFE(UTF- 16)和FFFE0000(UTF-32)在Unicode中都是未定义的码位,不应该出现在实际传输中。下表是各种UTF编码的BOM:

  UTF编码 ║ Byte Order Mark

  UTF-8  ║ EF BB BF

  UTF-16LE ║ FF FE

  UTF-16BE ║ FE FF

  UTF-32LE ║ FF FE 00 00

  UTF-32BE ║ 00 00 FE FF





5、java中使用的Unicode

5.1 内部编码

Java中,字符只以一种形式存在,那就是JVM内部的内部表示,Unicode码编号(U+0000~U+10FFFF)。JVM的唯一确定一个字符使得一个编码在进入jvm或者从jvm输出时需要进行编码转换。也就是编码转换只发生在JVM和OS以及网络传输的交互地带,也就是IO的各种byte或者reader/writer输入输出发生作用的地方。在JVM和OS以及网络流交互的时间,Reader和Writer只是适用默认编码进行了默认的编码转换,来转换为字符流。面向字符是指系统文件中的字符和内存中的要一致。而面向字节是要保证系统中的二进制内容和读入JVM内部的二进制内容要一致。



5.2 utf-16

总共17个平面

0x0000~0x10FFFF 1114112-2048=1112064个码位

Unicode已定义的码位是238605个

平面15之定义了占65534个码位的专用区,0xF0000~0xFFFFD。

平面16之定义了占65534个码位的专用区,0x100000~0x10FFFD。

平面0中定义了6400个专有区,0xE0000~0xF8FF。

238605-65534*2-6400-2408=99089余下的分布在平面0、1、2、14上。

平面0上定义了52080个字符;

平面1上定义了3419个字符;

平面2上定义了43253个字符;

平面14上定义了337个字符。

平面2的43253个字符都是汉字,平面0上定义了27973个汉字。

基本平面 0x0000~0xFFFF

1~14平面 0x10000~0xEFFFF 0xD800~0xDBFF高位 DCOO~DFFF低位

15平面 0xF0000~0xFFFFF 0xDB80~0xDBBF高位 DCOO~DFFF低位

16平面 0x100000~0x10FFFF 0xDBC0~0xDBFF高位 DCOO~DFFF低位



5.3高位序列和低位序列的判断

java.lang.String#getBytes(String) 的源代码

java.lang.StringCoding.encode 的源代码,再通过其底层类库的字符集类 sun.io.CharacterEncoding 可以找出 Unicode 的转换器,是采用 sun.io.CharToByteUnicode 这个类的,这个类的 sun.io 包是读取 file.encoding.pkg 这个系统属性拼接字符串反射而来的。



6、常见问题

6.1 通用UTF-8来编码

大量使用国外的开源软件时,UTF-8才是编码界最通用的语言。对英文是单字节、中文是三字节。在大量的英文存在的情况下高效。



6.2编码问题时查看

%javahome%/jre/lib/charsets.jar



6.3 语言的编码

C、C++、Python2内部字符串都是使用当前系统默认编码。

Python3、Java内部字符串用Unicode保存。

Ruby有一个内部变量$KCODE用来表示可识别的多字节字符串的编码,变量值为"EUC" "SJIS" "UTF8" "NONE"之一。$KCODE的值为"EUC"时,将假定字符串或正则表达式的编码为EUC-JP。同样地,若为"SJIS"时则认定为Shift JIS。若为"UTF8"时则认定为UTF-8。若为"NONE"时,将不会识别多字节字符串。在向该变量赋值时,只有第1个字节起作用,且不区分大小写字母。"e" "E" 代表 "EUC","s" "S" 代表 "SJIS","u" "U" 代表 "UTF8",而"n" "N" 则代表 "NONE"。默认值为"NONE"。即默认情况下Ruby把字符串当成单字节序列来处理。



6.4 js的unicode





6.5 网页编码

一个网页要在浏览器中正常显示,需要保持网页文件的编码、网页的meta标签声明(charset 来制定的其实是encoding编码而不是字符集)、浏览器编码设置是一致的。



6.6 联通乱码

在Win下的新建一个记事本文件,输入"联通"两个字之后,保存之后重新打开,发现出现乱码。这是因为GBK编码与UTF8编码产生了编码冲突。

从UNICODE到UTF8的转换规则:

Unicode UTF-8

0000 - 007F 0xxxxxxx

0080 - 07FF 110xxxxx 10xxxxxx

0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx



联的Unicode编码是[0x80] [0x54]

通的Unicode编码是[0x90] [0x1A]

8054和901A在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。使用第三种转换得到

[0xE8] [0x81] [0x94] [0xE9] [0x80] [0x9A],这就是其UTF8的编码。

新建一个文本文件时,记事本的编码默认是ANSI, 中文的就是GBK编码,而,此时"联通"的内码是:[0xC1] [0xAA] [0xCD] [0xA8]

C1 1100 0001

AA 1010 1010

CD 1100 1101

A8 1010 1000

其中联的两个字节、通的两个字节的起始部分的都是"110"和"10",与UTF8规则里的两字节模板是一致,所以再次用记事本打开时,记事本误认为这是一个UTF8编码的文件。按照反编码得到UNICODE的0x 006A,和0x0368,0x0368这个字符什么也不是,这就是"联通"两个字的文件没有办法在记事本里正常显示的原因。如果多几个字的输入话,由于记事本检测到不是合格的uft-8编码的字节转而会采用GBK,乱码又不出现。



6.7Google学习乱码

http://www.google.cn/search?hl=zh-CN&newwindow=1&q=学习

出现乱码。



6.8 java 编译时的乱码

对于不是平台默认编码的情况下,java源文件在编译时,需要指定源文件的编码,否则无法正常编译。

1、对于win下默认的GBK编码

C:\>javac SqlUtility.java



C:\>javac -encoding GBK SqlUtility.java



C:\>javac -encoding utf-8 SqlUtility.java

SqlUtility.java:24: 警告:编码 utf-8 的不可映射字符

* ????????????????

^

2、对于unicode的默认是utf-16

C:\>javac SqlUtility.java

SqlUtility.java:38: 非法字符: \0

C:\>javac -encoding utf-16 SqlUtility.java



3、对于utf-8的编码,win下需要删除文件头的二进制编码EFBBBF(因为它是由Unicode标准的FEFF,为了保证字节序而存在),并不是

C:\>javac SqlUtility.java

SqlUtility.java:1: 警告:编码 GBK 的不可映射字符

锘?**

^

SqlUtility.java:1: 非法字符: \65533

锘?**

^

1 错误

1 警告



C:\>javac -encoding utf-16 SqlUtility.java

SqlUtility.java:1: 非法字符: \61371

C:\>javac -encoding utf-8 SqlUtility.java

SqlUtility.java:1: 非法字符: \65279

?/**

^

1 错误



注:删除EFBBBF之后的

C:\>javac -encoding utf-8 SqlUtility.java







Unicode官方站点

http://www.unicode.org/

Unicode维基

http://zh.wikipedia.org/wiki/Unicode

字符与编码的发展过程

http://www.regexlab.com/zh/encoding.htm

Java 平台中的增补字符

http://java.sun.com/developer/technicalArticles/Intl/Supplementary/

Arrays的sort算法分析

1、Arrays.sort方法概述
Arrays提供了对基本类型byte、char、double、float、int、long型数组的排序实现。
Arrays提供了对对象类型数组的比较。
实现可比较接口的,可以直接进行比较:
public static void sort(Object[] a)进行比较
没有实现比较接口的,须有一个比较器,而且这个比较器可以进行对该种对象类型的比较:
public static void sort(T[] a,Comparator c)

而且以上的排序算法都提供了有区间的数据排序,formIndex和toIndex进行。


2、分析int[]的排序实现
基本类型同int[]相似
查看API可以发现sort整数数组提供以下两种方法实现:
//不带范围的
public static void sort(int[] a) {
sort1(a, 0, a.length);
}
//带范围的排序
public static void sort(int[] a, int fromIndex, int toIndex) {
rangeCheck(a.length, fromIndex, toIndex);//进行了范围检查
sort1(a, fromIndex, toIndex-fromIndex);
}

通过代码可以看出都是调用了以下函数进行了排序:
private static void sort1(int x[], int off, int len)


关于相关调用函数的说明
1)对输入参数进行了校验,保证入参的有效性;
##这样做的好处是,由于我们的计算机都是堆栈计算机,在传入参数时检验有效##性,可以避免数据压入栈的过程中的时间、空间消耗。应该是一种编程推荐的##做法。
private static void rangeCheck(int arrayLen, int fromIndex, int toIndex) {
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex+")");
if (fromIndex <> arrayLen)
throw new ArrayIndexOutOfBoundsException(toIndex);
}

2)交换数组的两个元素,采用普通的中间变量交换方法
##空间数据交换是一种普遍使用的方法;虽然可以使用时间换空间的方法,但一##般只是在空间资源紧缺的情况下使用。一般认为都是时间紧缺,占主导地位。
private static void swap(int x[], int a, int b)
{
int t = x[a];
x[a] = x[b];
x[b] = t;
}

3)交换数组中的连续的一组元素
##将下标a开始的n个元素与从下标b开始的n个元素,依次交换,调用swap
##方法进行实际的数据交换。
private static void vecswap(int x[], int a, int b, int n)
{
for (int i = 0; i <> x[c] ? b : x[a] > x[c] ? c : a));
}

核心函数分析
传入参数的说明:
X[]:要排序的数组
Int off:排序元素的起始位置
Int len:要排序元素的个数
private static void sort1(int x[], int off, int len)
{
// Insertion sort on smallest arrays
##插入排序
if (len < i =" off;" j =" i;"> off && x[j - 1] > x[j]; j--)
swap(x, j, j - 1);
return;
}

// Choose a partition element, v
##选择分割元素v

##找到位置在中间的元素
int m = off + (len >> 1); // Small arrays, middle element

##要排序元素个数大于7时的情况
##相对于else就只有等于7的情况
if (len > 7)
{
int l = off;
int n = off + len - 1;
if (len > 40)
{
// Big arrays, pseudomedian of 9
int s = len / 8;
l = med3(x, l, l + s, l + 2 * s);
m = med3(x, m - s, m, m + s);
n = med3(x, n - 2 * s, n - s, n);
}
##取得假设的中间值
m = med3(x, l, m, n); // Mid-size, med of 3
}
int v = x[m];

// Establish Invariant: v* (v)* v*
int a = off, b = a, c = off + len - 1, d = c;
##a为起始元素位置,b跟a相同,d为结束元素位置;c同d
##a,d是固定不变量,作为界定元素;b,c是自变量

while (true)
{
while (b <= c && x[b] <= v) { if (x[b] == v) swap(x, a++, b); b++; } while (c >= b && x[c] >= v)
{
if (x[c] == v)
swap(x, c, d--);
c--;
}
if (b > c)
break;
swap(x, b++, c--);
}

// Swap partition elements back to middle
int s, n = off + len;
##交换
s = Math.min(a - off, b - a);
vecswap(x, off, b - s, s);
##交换
s = Math.min(d - c, n - d - 1);
vecswap(x, b, n - s, s);

// Recursively sort non-partition-elements
##排序a-b之间的元素
if ((s = b - a) > 1)
sort1(x, off, s);
##排序c到d之间的元素
if ((s = d - c) > 1)
sort1(x, n - s, s);
}

核心的本质使用的是采用的对快速排序的一种改进,分段进行操作。
找到一个模拟的中间值(数值意义上的),按照中间值分段进行排序。
最后分成的小段使用的是快速排序

3、对象实现了可比较接口Comparable 进行比较
INSERTIONSORT_THRESHOLD表示插入排序的起始点,当长度小于此临界值时,进行插入排序。
整体采用的是2—路归并排序。
对前一半和后一半分别使其有序,然后进行合并。
此算法是稳定的,O( nlog2(n))的效率

/**
* used in preference to mergesort or quicksort.
*/
private static final int INSERTIONSORT_THRESHOLD = 7;

private static void mergeSort(Object[] src, Object[] dest,
int low, int high, int off) {
int length = high - low;
// Insertion sort on smallest arrays
if (length < i="low;" j="i;">low &&
((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
// Recursively sort halves of dest into src
int destLow = low;
int destHigh = high;
low += off;
high += off;
int mid = (low + high) >>> 1;
##递归对前一半和后一半进行归并排序
mergeSort(dest, src, low, mid, -off);
mergeSort(dest, src, mid, high, -off);

// If list is already sorted, just copy from src to dest. This is an
// optimization that results in faster sorts for nearly ordered lists.
#前一半和后一半分别有序,并且前一半的最大值小于等于后一半的最小值,那么直接进行合并
if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) { System.arraycopy(src, low, dest, destLow, length); return; } // Merge sorted halves (now in src) into dest #否则对该段进行比较的合并操作 #p 表示前一段的下标 #q 表示后一段的下标 for(int i = destLow, p = low, q = mid; i <>=high表示后一半数据已经全部排序完了,将前一半剩余的数据复制到目标中
#2、p<>= high p <>=mid前一半数据已经全部排序完了,将后一半剩余的数据复制到目标中
#2、p src[q] 说明前一段数据中的p大于q,那么复制后一段数据中的q到目标
else
dest[i] = src[q++];
}
}

/**
* Swaps x[a] with x[b].
*/
private static void swap(Object[] x, int a, int b) {
Object t = x[a];
x[a] = x[b];
x[b] = t;
}

4、对象没有可比较性,进行比较时需要靠比较器进行
INSERTIONSORT_THRESHOLD表示插入排序的起始点,当长度小于此临界值时,进行插入排序。
整体采用的是2—路归并排序。
对前一半和后一半分别使其有序,然后进行合并。
此算法是稳定的,O( nlog2(n))的效率.

此算法分析与前相同,仅仅是在比较的时间是采用的比较器,两个对象做参数

private static void mergeSort(Object[] src,
Object[] dest,
int low, int high, int off,
Comparator c) {
int length = high - low;

// Insertion sort on smallest arrays
if (length < i="low;" j="i;">low && c.compare(dest[j-1], dest[j])>0; j--)
swap(dest, j, j-1);
return;
}

// Recursively sort halves of dest into src
int destLow = low;
int destHigh = high;
low += off;
high += off;
int mid = (low + high) >>> 1;
mergeSort(dest, src, low, mid, -off, c);
mergeSort(dest, src, mid, high, -off, c);

// If list is already sorted, just copy from src to dest. This is an
// optimization that results in faster sorts for nearly ordered lists.
if (c.compare(src[mid-1], src[mid]) <= 0) { System.arraycopy(src, low, dest, destLow, length); return; } // Merge sorted halves (now in src) into dest for(int i = destLow, p = low, q = mid; i <>= high p < mid && c.compare(src[p], src[q]) <= 0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
}

5、以上排序算法是qsort和合并排序算法的实现
快速排序是不稳定的。
合并排序是稳定的。

排序是一个常用的算法,尤其是在其他一些使用的过程中常常需要先进行此操作,例如查找
public static int binarySearch() 二分查找这个是基于排序的

基本型的排序算法是
是一个经过调优的快速排序法,改编自 Jon L. Bentley 和 M. Douglas McIlroy 合著的 Engineering a Sort Function", Software-Practice and Experience Vol. 23(11) P. 1249-1265 (November 1993)。此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
该算法原文如下
http://www.enseignement.polytechnique.fr/profs/informatique/Luc.Maranget/421/09/bentley93engineering.pdf

分析该篇论文,可以看出排序算法的改进方向,进一步掌握如何获取改进算法的一个思路。



References
http://www.docin.com/p-26519551.html
http://hi.baidu.com/helloyanwo/blog/item/bd39af6ce372a1f142169409.html
http://www.cuyoo.com/html/shenghuo/2009/0304/1015.html
http://nknucc.nknu.edu.tw/~jwu/datastr/datastr.htm
http://www.enseignement.polytechnique.fr/profs/informatique/Luc.Maranget/421/09/bentley93engineering.pdf
http://cs.umaine.edu/~chaw/200801/capstone/n/enggsort.pdf