字符编码: ANSI/UTF-8/UCS2(UTF-16),以及回车换行

来源:互联网 发布:java webclient post 编辑:程序博客网 时间:2024/04/29 09:35
最近遇到一个linux 平台上invisible character (0x1d) 引起的数据装载失败问题,正好借此机会整理一下字符编码的相关知识

回车/换行:

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

顾名思义,回车和换行是两个不同的控制字符:

    - 回车(Carriage Return)即\r,ascii码13(0x0d),作用是将光标移到一行的开始位置

    - 换行(Line Feed)即\n,ascii码10(0x0a),作用是将光标移到下一行

在不同的操作系统平台上,默认是用不同的控制符来标志一行的结束:

    - Windows: \r\n

    - Linux/Unix: \n

    - Mac: \r (据说近来也改成了\n)

这种不同实现导致的结果就是Winodws上的标准文本文件在其它平台上会多出一个^M控制符,而其它平台的文件在Windows上看会只有一行。Linux上有dos2unix/unix2dos命令来解决文本换行的问题。

 

字符编码:

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

文字是人类的语言,对于计算机来讲,语言就是0和1而已,因此将字符保存在计算机里就会涉及到字符编码的问题。网上有许多文章和博客介绍不同字符编码方式,这里就不引用了。列一些我自己理解的重点:

ASCII:ANSI

最初的计算机存储有限,字符的存储使用的是8位的ASCII码(最高位为0),最多可以存储128个字符,加上扩展ASCII码(最高位为1)也只能存储256个字符。于是,各国都制定了自己的兼容ascii编码规范,就是各种ANSI码,来表示不同的语言中的不同文字。于是同样的ASCII码在不同的字符集(Collation/CharSet/Code Page)上就可以表示不同的字符,这就是为什么ANSI字符一定要与特定的Collation绑定才能表示唯一正确的字符,绑定错了就可能出现乱码(ascii码小于128的字符不会乱码)。

 

Unicode

又名万国码,所有语言的所有字符都可以用唯一的Unicode码来标识。Unicode有几种不同的实现方式:

    - UTF-8: 按照字节(8位)来存储编码,长度可变(1~6字节)。完全兼容ascii码,即标准ascii码中的字符在unicode中也是以相同的码来表示的。UTF-8 使用第一个字节确定字节数:第一个字节首为0即一个字节,110即2字节,1110即3字节,字符后续字节都用10开始。

    - UTF-16 (UCS2):按照双字节存储字符,因此有字节序问题:即高位在前(Big Endian)还是低位在前(Little Endian)。这个行为与CPU处理字节方式有关,一般Little Endian用得比较多。比如“汉”字,Unicode码为0x6C49,按照Big Endian存储就是6C49,Little Endian则为496C。

 

如何区分不同编码的文件:

用UltraEdit或者类似编辑器打开文本文件,切换到Hex Mode(ctrl + h),看文件头:

    - 没有任何特殊文件头的,直接第一个字符就是文本内容的,是ansi文件

    - 以BOM (Byte Order Mark) 开头的是否Unicode文件。BOM可以是0xFEFF(表示Big Endian)、0XFFFE(表示Little Endian)、或者0xEFBBBF (UTF-8)。注意,在Windows上用Notepad保存一个UTF-8的文件,也是以0xFFFE开头的。

 

Unicode与SQL Server:

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

UTF-8是目前使用最广泛的Unicode编码格式,但是SQL Server使用的是UTF-16作为Unicode字符的编码格式,即NCHAR/NTEXT/NVARHCAR存储的都是双字节编码的Unicode字符,以N为前缀(比如N'Hello')的字符串即为双字节Unicode字符。对于UTF-8字符串,如果它只包含ANSI字符,则可以按照ANSI串处理;否则的话必须将UTF-8串转换成UTF-16、再交给SQL Server 处理。这种行为可能带来两个常见的问题:

    - 许多程序(包括ASP.NET)在输出文件的时候都使用UTF-8格式。如果这些文件需要被SQL Server正确地处理,必须进行转码至UTF-16。

    - SQL 2005以后支持对Native XML进行处理,但是如果XML文档本身是UTF-8编码,就没办法直接处理。下面是一个例子:

    在SSMS中运行如下代码,会得到错误9420:

    DECLARE @Inf XML      
    SET     @Inf = '<?xml version="1.0" encoding="utf-8"?>
                     <root>
                        <names>
                             <name>高</name>
                         </names>
                         <names>
                             <name>帅</name>
                         </names>
                     </root>
                    ' 
     
    SELECT  x.value('name[1]', 'VARCHAR(10)') AS Name 
    FROM    @Inf.nodes('/root/names') AS t(x)

 

    即使在给@Inf 赋值时强制指定N前缀转换成Unicode也不行,因为XML文档本身指定了UTF-8编码,解决的方法只有在XML文档中指定UTF-16编码并且指定N前缀:

        DECLARE @Inf XML      
    SET     @Inf = N'<?xml version="1.0" encoding="utf-16"?>
                     <root>
                        <names>
                             <name>高</name>
                         </names>
                         <names>
                             <name>帅</name>
                         </names>
                     </root>
                    ' 
     
    SELECT  x.value('name[1]', 'VARCHAR(10)') AS Name 
    FROM    @Inf.nodes('/root/names') AS t(x) 

 

关于看不见的字符0x1D

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

这个字符为GROUP SEPARATOR,作用是分组,怎么用这个控制字符取决于应用程序。在MS SQL和SSIS中,这个字符并没有特别含义;但是Netezza确实把这个字符当作分隔符使用,导致在数据导入时分栏错误。遗憾的是Netezza的EXTERNAL TABLE导入时没办法控制batch size,所以排查这种问题比较麻烦。对于控制字符,可以在引用EXTERNAL DataSource的时候加上CTRLCHARS TRUE选项,则可以将控制字符作为字段内容录入。