汉字与区位码互转(天天使用Delphi的String存储的是内码,Windows记事本存储的文件也是内码),几个常见汉字的各种编码,utf8与unicode的编码在线查询,附有读书笔记 good

汉字与区位码(1) – 转换函数

汉=BABA(内码)=-A0A0=2626(区位码)
字=D7D6(内码)=-A0A0=5554(区位码)

  1. unit Unit1;  
  2.   
  3. interface  
  4.   
  5. uses  
  6.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,  
  7.   Dialogs, StdCtrls;  
  8.   
  9. type  
  10.   TForm1 = class(TForm)  
  11.     Button1: TButton;  
  12.     Button2: TButton;  
  13.     procedure Button1Click(Sender: TObject);  
  14.     procedure Button2Click(Sender: TObject);  
  15.   end;  
  16.   
  17. var  
  18.   Form1: TForm1;  
  19.   
  20. implementation  
  21.   
  22. {$R *.dfm}  
  23.   
  24. {用十六进制查看内存的函数; 参数1是内存起点, 参数2是以字节为单位的长度}  
  25. function ToHex(p: PByteArray; bit: Integer): string;  
  26. var  
  27.   i: Integer;  
  28. begin  
  29.   for i := 0 to bit – 1 do  
  30.     Result := IntToHex(p^[i], 2) + Chr(32) + Result;  
  31.   Result := TrimRight(Result);  
  32. end;  
  33.   
  34. {用二进制查看内存的函数; 参数1是内存起点, 参数2是以字节为单位的长度}  
  35. function ToBin(p: PByteArray; bit: Integer): string;  
  36. const  
  37.   Convert: array[‘0′..’F’] of string = (  
  38.     ‘0000’, ‘0001’, ‘0010’, ‘0011’, ‘0100’, ‘0101’, ‘0110’, ‘0111’, ‘1000’, ‘1001’,  
  39.     ”, ”, ”, ”, ”, ”, ”, ‘1010’, ‘1011’, ‘1100’, ‘1101’, ‘1110’, ‘1111’);  
  40. var  
  41.   i: Integer;  
  42.   s: string;  
  43. begin  
  44.   s := ToHex(p, bit);  
  45.   for i := 1 to Length(s) do  
  46.     if s[i] <> Chr(32) then  
  47.       Result := Result + Convert[s[i]]澳门新葡萄京娱乐场,  
  48.     else  
  49.       Result := Result + Chr(32);  
  50. end;  
  51.   
  52. {测试一}  
  53. procedure TForm1.Button1Click(Sender: TObject);  
  54. var  
  55.   num: Integer;  
  56. begin  
  57.   Randomize;  
  58.   num := Random(MaxInt);  
  59.   ShowMessage(IntToStr(num) + #10#13#10#13 +  
  60.               ToHex(@num, 4) + #10#13#10#13 +  
  61.               ToBin(@num, 4));  
  62. end;  
  63.   
  64. {测试二}  
  65. procedure TForm1.Button2Click(Sender: TObject);  
  66. var  
  67.   str: string;  
  68. begin  
  69.   str := ‘Delphi 2010’;  
  70.   ShowMessage(str + #10#13#10#13 +  
  71.               ToHex(@str[1], Length(str)*SizeOf(str[1])) + #10#13#10#13 +  
  72.               ToBin(@str[1], Length(str)*SizeOf(str[1])));  
  73. end;  
  74.   
  75. end.  

先上转换函数:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject,’,’,’);
procedure Button2Click(Sender: TObject,’,’,’);
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

{查汉字区位码}
function Str2GB(const s: AnsiString): string;
const
G = 160;
begin
Result := Format(‘%d%d’, [Ord(s[1])-G, Ord(s[2])-G],’,’,’);
end;

{通过区位码查汉字}
function GB2Str(const n: Word): string;
const
G = 160;
begin
Result := string(AnsiChar(n div 100 + G) + AnsiChar(n mod 100 +
G),’,’,’);
end;

{测试}
procedure TForm1.Button1Click(Sender: TObject,’,’,’);
begin
ShowMessage(GB2Str(4582),’,’,’); {万}
end;

procedure TForm1.Button2Click(Sender: TObject,’,’,’);
begin
ShowMessage(Str2GB(‘万’),’,’,’); {4582}
end;

各种编码查询表:

end.

 

获取区位码表: 准备个 Memo 接收(注意使用了上面的函数)

var
i,j: Byte;
s: string;
begin
s := ”;
for i := 1 to 94 do
begin
for j := 1 to 94 do
s := Format(‘%s %s’, [s, GB2Str(i*100 + j)],’,’,’);
Memo1.Lines.Add(s,’,’,’);
s := ”;
end;
end;

汉字与区位码(2) – 分析

在没有 Uncode 的时代, 用 256 个 ACSII 只是方便了英文, 其他文字怎么办?

那时是各自为政的, 譬如中文就有:
GB2312-80(国内简体)、Big5(台湾繁体)、HKSCS(香港繁体), 但它们互不兼容.

GB2312(1980年) 后来升级到 GBK(1995年), 现在电脑上使用的是
GB18030(2000年), 这个系列是向后兼容的.

区位码的概念是在 GB2312 时提出的, GB2312 是一个 94*94 的二维表, 行就是
“区”、列就是 “位”, 譬如 “万” 字在 45 区 82 位, 所以 “万” 字的区位码是:

00-09 区(682个): 是符号、数字、英文字符…制表符等;
10-15 区: 空白, 留待扩展;
16-55 区(3755个): 常用汉字(也有叫一级汉字), 按拼音排序;
56-87 区(3008个): 非常用汉字(也有叫二级汉字), 这是按部首排序的;
88-94 区: 空白, 留待扩展.

还有两个概念: 国际码、内码.

先转一下话题: 打开记事本输入 “万” 字, 保存(编码选择 ANSI,’,’,’);
然后用二进制编辑器(譬如: UltraEdit) 打开, 会看到:
CD F2, 这就是 “万” 字的内码!

那什么又是国际码呢?
咱们的 GB2312 用一个二维表表示了咱们需要的字符, 其他文字可能也是如此;
为了区别, 所以有国际组织规定把咱们的 “区” 和 “位” 分别加上
32(十六进制表示: $20; 二进制表示: 00100000)
作为国际码(那其他文字应该加另外一个不同的数字).
这样我们可以算出(45+32, 82+32):
“万” 字的国际码是 77 114($4D72)

不过这还不能在计算机上使用, 因为这样会和早已通用的 ASCII
码混淆(导致乱码), 譬如: 77 是 ASCII 的 “M”, 114 是 ASCII 的 “r”.
所以又有规定把每个字节的最高位都从 0 换成 1(这之前它们都是 0),
或者说把每个字节(区和位)都再加上 128(十六进制的: $80; 二进制的:
10000000), 从而得到 “机内码”, 也就是前面所说的 “内码”.

总结一下: 从区位码, 区和位分别 +32 得到国际码, 再分别 +128 得到内码;
简化一下: 区位码的区和位分别 +160 即可得到内码, 用十六进制表示: 区位码 +
$A0A0 = 内码.

验证一下前面从记事本输入得到的 CD F2:
45 + 160 = 205; (205 就是 十六进制的 $CD)
82 + 160 = 242; (242 就是 十六进制的 $F2)

这样, 内码的两个字节的最高位就都是 1 了, 另外 ASCII 的(0-254)最高位都是
0, 所以有人也使用这个特点来区别汉字.

虽然 Window 2000 开始, 系统已经使用 Uncode 编码了,
其实咱们现在还是使用的这种双字节内码,
这是系统根据我们选择的字符集自动转换的.

汉(记住它,以后碰到内存里的数值,就会有敏感性了,会方便测试)
utf8 = E6 B1 89
unicode = 6C 49 ,在Delphi2010的dfm里存储的是它的十进制 27721
GBK = BA BA


utf8 = E5 90 B4
unicode = 54 34 ,在Delphi的dfm里存储的是它的十进制 21556
GBK = CE E2

“啊”字是GB2312之中的第一个汉字,会以两个字节,0xB0(第一个字节)0xA1(第二个字节)储存(后面会以这个字来解释理论)。
汉=BABA=47802
字=D7D6=55254
中=D6D0=54992
文=CEC4=52932
华=BBAA=48042
夏=CFC4=53188
吴=CEE2=52962
A=65
€=128
À=192
æ=230

GBK里特有的字:
在GB 2312-80推出以后才简化的汉字(如“啰”)
部分人名用字(如中国前总理朱镕基的“镕”字)
GBK3扩充区的第一个汉字“丂”的ANSI编码是8140H,这一点是经过理论和实践双验证的。

GBK的存储方式是大头存储,但Unicode是小头存储,参考:


//汉字转区位码
function Str2GB(const S: AnsiString): string;
const G = 160; // 160 = hA0
var n, m: word;
begin
n := Ord(S[1]);
m := Ord(S[2]);
Result := FormatFloat(’00’, n-G) + FormatFloat(’00’, m-G);
end;

//区位码转汉字
function GB2Str(const n: Word): string;
const G = 160;
begin                             //前2位数                          
//后2位数
Result := string(AnsiChar(n div 100 + G) + AnsiChar(n mod 100 + G));
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(GB2Str(StrToInt(Edit1.Text)));
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
ShowMessage(Str2GB(AnsiString(Edit2.Text)));
end;


特别注意,这是D7-XE7都可以使用的程序。因为ANSI与Unicode的区别仅仅在于,ANSI英文表示是一个字符,Unicode的英文是两个字符。但ANSI和Unicode处理中文的时候,都是两个字符,且两者内容完全一致。这么说ANSI与Unicode对汉字的处理几乎没有区别,区别在于对英文字符的处理,并且Unicode下还能处理除了中文以外的语言的特殊字符(比如俄文字符)。另外各个不同的ANSI编码之间那就真的是完全不同、鸡对鸭讲了。

===================================================

总结:这说明平时天天用到Delphi的String,存储的是汉字的内码(不是区位码)。理论解释:汉字机内码,又称“汉字ASCII码”,简称“内码”,指计算机内部存储,处理加工和传输汉字时所用的由0和1符号组成的代码。输入码被接受后就由汉字操作系统的“输入码转换模块”转换为机内码,与所采用的键盘输入法无关。机内码是汉字最基本的编码,不管是什么汉字系统和汉字输入方法,输入的汉字外码到机器内部都要转换成机内码,才能被存储和进行各种处理。

前面是使用的是“内码”和“区位码”,其实还有一个“国际码”,关系如下:
内码(Delphi的String使用的编码)=
国标码(国家定义)+8080H(其实就是强行添加最高位,使最高位为1)=
区位码(国家定义的基础表格)+A0A0H(比国标码多加了2020H,可以使用Windows自带的区位码输入法测试输入)

出现最高位的原因是:
汉字处理系统要保证中西文的兼容,当系统中同时存在ASCII码和汉字国标码时,将会产生二义性。例如:有两个字节的内容为30H和21H,它既可表示汉字“啊”的国标码,又可表示西文“0”和“!”的ASCII码。为此,汉字机内码应对国标码加以适当处理和变换。国标码的内码为二字节长的代码,它是在相应国标码的每个字节最高位上加“1”。

出现国标码的原因是:
GB2312-80
GB2312将代码表分为94个区,对应第一字节;每个区94个位,对应第二字节,两个字节的值分别为区号值和位号值加32(20H),因此也称为区位码。(读书笔记:94=5EH,这个值远小于128,因此加上20H等于7EH=126,因此再做变换没关系。而且我查了具体的Word文件,最后一项编码就是5E,而不是5F,这只能说GB2312定义的字符太少了,没有充分利用所有的空间。而且我特别注意到,每一个区的最后一行的低位F位置,确实没有定义任何汉字。问题,为什么要做变换?回答:查完基础表以后,再加上2020H就是国标码,政府就是这么规定的,没什么理由。为了方便和快速处理,实际编程使用最方便计算机标识的编码——内码,来使用,就可以直接标识是否汉字。区位码和国标码只是一种理论解释和定义,对程序员来说其实没什么用的。)

国标码是汉字信息交换的标准编码,但因其前后字节的最高位为0,与ASCII码发生冲突(读书笔记:国标码定义有道理,但不实用。另外我查了一下网上的GB2312的Word文件,第一个字符就是A1A1,即已经加好了A0A0的内码,这样虽然对程序员更实用,但这个表格其实已经是被加工过的,而不是国家最初定义的从零开始的基础表格),如“保”?字,国标码为31H和23H,而西文字符“1”和“#”的ASCII也为31H和23H,现假如内存中有两个字节为31H和23H,这到底是一个汉字?,还是两个西文字符“1”和“#”于是就出现了二义性,显然,国标码是不可能在计算机内部直接采用的,于是,汉字的机内码采用变形国标码。
其变换方法为:将国标码的每个字节都加上128,即将两个字节的最高位由0改1,其余7位不变,如:由上面我们知道,“保”字的国标码为3123H,前字节为00110001B,后字节为00100011B,高位改1为10110001B和10100011B
即为B1A3H,因此,汉字的机内码就是B1A3H。

参考:

举例来说,“啊”字是GB2312之中的第一个汉字,它的区位码就是1601。
例如“啊”字在大多数程序中,会以两个字节,0xB0(第一个字节)0xA1(第二个字节)储存。(与区位码对比:0xB0=0xA0+16,0xA1=0xA0+1)。


下一个问题:测试一下QT存储的是什么码?我猜是内码的Unicode标识。因此QT字符串与Unicode版Delphi字符串应该兼容的(Delphi字符串头部在负方向,QT看不到)


字符转换成UTF8:

字符转换成Unicode:

GBK编码列表

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注