本文目标:

  • 了解Delphi的字符串类型
  • 字符编码的检测与转换
  • 简体繁体转换

0. 导言

看完“.Net与字符编码(理论篇)”,我们明白了字符是自然语言中的最小单位,在存储和传输的过程中可以使用三种编码方法:ASCII、DBCS以及Unicode。常见的DBCS编码有GB2312、GBK和BIG5,而UTF-8、UTF-16和UTF-32则是最常用的Unicode编码类型。

1. 字符串类型

在Delphi中有两种字符串类型:AnsiStringWideString。AnsiString被称为“长字符串”(Long String);WideString则叫做“宽字符串”(Unicode String),它和COM String (BSTR)兼容。它们都是由程序在(Heap)上分配的并自动管理内存的分配和释放。目前在Win32平台上,string类型等同于AnsiString。AnsiString还可以理解成字节序列,它支持单字节字符编码(SBCS)、多字节字符编码(MBCS/DBCS)以及UTF-8编码。而WideString使用UTF-16编码,完美支持Unicode。

为了说明字符和字节的区别,我们来看一个计算字符个数的例子:

// 假设当前系统页为CP936(GBK 1.0)
procedure TestAnsiLength;
var
  str: string;
begin
  str := '汉字ABC';
  Assert(Length(str) = 7);      // 7个字节
  Assert(AnsiLength(str) = 5);  // 5个字符
end;

下面是AnsiLength的两种实现:

// uses SysUtils;
function AnsiLength(const s: string): integer;
var
  p, q: PChar;
begin
  Result := 0;
  p := PChar(s);
  q := p + Length(s);
  while p < q do
  begin
    Inc(Result);
    if p^ in LeadBytes then // 当前系统代码页的前导字节数组
      Inc(p, 2)
    else
      Inc(p);
  end;
end;
// uses Windows;
function AnsiLength(const s: string): Integer;
begin
  Result := MultiByteToWideChar(CP_ACP, 0, PAnsiChar(s), -1, nil, 0);
  if Result > 0 then Dec(Result);  // 除去终止符
end;

如果理解了.Net与字符编码(理论篇)中的编码知识,上面的例子还是很简单的。

2. 字符编码的检测与转换

“工欲善其事,必先利其器”,我先向大家推荐一些工具:

定义基本的类型:

  { 编码类型 }
  TEncodingType = (
    etAnsi,       // ANSI   format (SBCS/DBCS)
    etUTF8,       // UTF-8  format
    etUnicode,    // UTF-16 format using little endian
    etUnicodeBE,  // UTF-16 format using big endian
    etUTF32,      // UTF-32 format using little endian
    etUTF32BE     // UTF-32 format using big endian
  );

  { 字节顺序标记 }
  TByteOrderMask = array of Byte;

获得不同编码类型的BOM:

CopyBytes

function TryGetBOM(const encodingType: TEncodingType; var bom: TByteOrderMask): Boolean;
begin
  Result := True;
  case encodingType of
    etUTF8:      CopyBytes(BOM_Utf8, bom);
    etUnicode:   CopyBytes(BOM_UTF16_LSB, bom);
    etUnicodeBE: CopyBytes(BOM_UTF16_MSB, bom);
    etUTF32:     CopyBytes(BOM_UTF32_LSB, bom);
    etUTF32BE:   CopyBytes(BOM_UTF32_MSB, bom);
    else
    begin
      SetLength(bom, 0);
      Result := False;
    end;
  end;
end;

检测字符编码类型:

CompareBOM
function DetectEncoding(buffer: PAnsiChar): TEncodingType; overload;
begin
  if CompareBOM(buffer, BOM_UTF8) then
    Result := etUTF8
  else if CompareBOM(buffer, BOM_UTF16_LSB) then
    Result := etUnicode
  else if CompareBOM(buffer, BOM_UTF16_MSB) then
    Result := etUnicodeBE
  else if CompareBOM(buffer, BOM_UTF32_LSB) then
    Result := etUTF32
  else if CompareBOM(buffer, BOM_UTF32_MSB) then
    Result := etUTF32BE
  else
    Result := etAnsi;
end;

function DetectEncoding(stream: TStream): TEncodingType; overload;
var
  pos: Int64;
  bytes: TByteOrderMask;
begin
  SetLength(bytes, 6);
  ZeroMemory(@bytes[0], Length(bytes));
  pos := stream.Seek(0, soFromCurrent);
  stream.Seek(0, soFromBeginning);
  stream.Read(bytes[0], SizeOf(bytes));
  stream.Seek(pos, soFromBeginning);
  Result := DetectEncoding(PAnsiChar(@bytes[0]));
end;

下面的方法演示了如何用不同的编码类型来保存文本:

procedure WriteText(stream: TStream; const buffer: WideString;
  const encodingType: TEncodingType; withBom: Boolean = False);
var
  s: AnsiString;
  p: PAnsiChar;
  bom: TByteOrderMask;
  bytes: Integer;
begin
  p := nil;
  bytes := Length(buffer) * SizeOf(WideChar);
  if withBom and TryGetBOM(encodingType, bom) then
  begin
    stream.Write(bom[0], Length(bom));
  end;  
  case encodingType of
    etAnsi:
    begin
      p := PAnsiChar(buffer);
      bytes := Length(buffer);
    end;
    etUTF8:
    begin
      s := Utf8Encode(buffer);
      p := PAnsiChar(s);
      bytes := Length(s);
    end;
    etUnicode:
    begin
      p := PAnsiChar(PWideChar(buffer));
    end;
    etUnicodeBE:
    begin
      StrSwapByteOrder(PWideChar(buffer));
      p := PAnsiChar(PWideChar(buffer));
    end;
    else  // 留给读者去实现
    begin
      raise Exception.Create('Not Implemented.');
    end;
  end;
  stream.Write(p^, bytes);
end;

需要说明的是,如果把这些过程封装成对象的话,结构会更清晰。

3. 简体繁体转换

简体繁体转换包括简转繁繁转简两种情况,其原理是利用查找字符编码映射表来查找相应的字符。网上有一个“利用编码对照表完成内码转换和简繁体转换的单元”就是基于这个原理写的,在这里就暂不详述了。

{ TODO: 采用OOP来封装字符编码模块,并提供下载 }
{ TODO: 研究简体繁体转换 }

参考文章

http://www.cnblogs.com/baoquan/articles/1027371.html

Delphi与字符编码(实战篇)(MultiByteToWideChar会返回转换后的宽字符串长度)的更多相关文章

  1. 字符编码和python使用encode,decode转换utf-8, gbk, gb2312

    ASCII码 标准ASCII码使用7位二进制数表示大写或小写字母,数字0到9标点符号以及在美式英语中使用的特殊控制字符. 在标准ASCII码中,最高位(b7)用作奇偶校验位,所谓奇偶校验,是指在代码传 ...

  2. 关于Unicode,字符集,字符编码,每个程序员都应该知道的事

    关于Unicode,字符集,字符编码,每个程序员都应该知道的事 作者:Jack47 李笑来的文章如何判断一个人是否聪明?中提到: 必要.清晰.且准确的概念,是一切思考的基石.所谓思考,很大程度上,就是 ...

  3. 关于Unicode,字符集,字符编码

    基本概念 字符[character] 字符代表了字母表中的字符,标点符号和其他的一些符号.在计算机中,文本是由字符组成的. 字符集合[character set] 由一套用于特定用途的字符组成,例如支 ...

  4. 字符编码之间的相互转换 UTF8与GBK(转载)

    转载自http://www.cnblogs.com/azraelly/archive/2012/06/21/2558360.html UTF8与GBK字符编码之间的相互转换 C++ UTF8编码转换 ...

  5. 【miscellaneous】【C/C++语言】UTF8与GBK字符编码之间的相互转换

    UTF8与GBK字符编码之间的相互转换 C++ UTF8编码转换 CChineseCode 一 预备知识 1,字符:字符是抽象的最小文本单位.它没有固定的形状(可能是一个字形),而且没有值." ...

  6. python标准库之字符编码详解

    codesc官方地址:https://docs.python.org/2/library/codecs.html 相关帮助:http://www.cnblogs.com/huxi/archive/20 ...

  7. JAVA的字符编码及问题

    web开发时,字符编码及有时候也会是一个麻烦的问题,没有经验的话,肯定不知道怎么解决,有一定的经验的话,那还是比较简单的.以下,是我学习过程中总结出来的几种字符编码级问题和其解决的方法 1.文档乱码, ...

  8. 001. Java内存中的字符编码

    Java内存中的字符编码 Unicode字符集及utf-8 .utf-16.utf-32 等字符编码方式 字符集:字符表示的数字集合,元素称为码点或码位: 字符编码:字符实际的储存表示: 码点:一个码 ...

  9. 字符编码(续)---Unicode与ANSI字符串转换以及分辨字符编码形式

    Unicode与ANSI字符串转换 我们使用windows函数MultiByteToWideChar将多字节字符串转换为宽字符字符串,如下: int MultiByteToWideChar( UINT ...

随机推荐

  1. Linux下MySQL 5.6的修改字符集编码为UTF8(彻底解决中文乱码问题)

    一.登录MySQL查看用SHOW VARIABLES LIKE ‘character%’;下字符集,显示如下:+--------------------------+----------------- ...

  2. 大端模式&小端模式、主机序&网络序、入栈地址高低问题

    一.大端模式&小端模式 所谓的“大端模式”,是指数据的低位(就是权值较小的后面那几位)保存在内存的高地址中,而数据的高位,保存在内存的低地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处 ...

  3. SQL Server 索引的自动维护 <第十三篇>

    在有大量事务的数据库中,表和索引随着时间的推移而碎片化.因此,为了增进性能,应该定期检查表和索引的碎片,并对具有大量碎片的进行整理. 1.确定当前数据库中所有需要分析碎片的表. 2.确定所有表和索引的 ...

  4. linux命令行常用快捷键

    方向          <-前               后 ->删除ctrl + d      删除光标所在位置上的字符相当于VIM里x或者dlctrl + h      删除光标所在 ...

  5. UESTC_Judgment Day CDOJ 11

    Today is the judgment day. The world is ending and all man will pay for their guilt and sin. Now the ...

  6. PKU 3667 Hotel(线段树)

    Hotel The cows are journeying north to Thunder Bay in Canada to gain cultural enrichment and enjoy a ...

  7. sendto() 向广播地址发包返回errno 13, Permission denied错误

    http://blog.csdn.net/guanghua2_0beta/article/details/52483916 sendto() 向广播地址发包返回errno 13, Permission ...

  8. poj 3308 (最大流)

    题意:n*m的地图,给出L个火星人登陆的坐标,要在火星人登陆地球的瞬间全部消灭他们,有一种激光枪,一次可以消灭一行(或一列),消灭一行(或一列)有不同的代价,总代价是所有激光枪的代价之积. 思路:之前 ...

  9. Ruby中,&:(ampersand colon)的用法

    前几日看Ruby代码,发现一个奇怪的用法,如下: a=['a', 'b', 'c'].map! &:upcase p a #["A", "B", &qu ...

  10. CSS清除浮动_清除float浮动

    2.clear:both清除浮动为了统一样式,我们新建一个样式选择器CSS命名为“.clear”,并且对应选择器样式为“clear:both”,然后我们在父级“</div>”结束前加此di ...