回 帖 发 新 帖 刷新版面

主题:打开、保存Unicode或UFT-8编码文件的源代码

打开、保存Unicode或UFT-8编码文件的源代码


1.Unicode编码文件的读写技术简述
Unicode编码是两字节的全编码,对于Ascii字符它也使用两字节表示,代码页通过高字节的取值范围来确定是Ascii字符,还是汉字的高字节。
Unicode编码的文本文件开头两个字节是 &HFF 和 &HFE,这是Unicode头,即Unicode编码文件标记。下面就简单介绍如何在自编的文本编辑器中实现Unicode编码文本文件的读写。
我们知道VB的字符串在内存中实际上就是以Unicode编码形式保存的,但当程序将字符串写入txt文件时,已经自动将之转变为 Ansi格式的编码形式,所以我们要将文本保存为Unicode编码的文件的话,就不能直接写入这个字符串,而应先将该字符串赋值给一个Byte型的数组,再将这个数组原封不动地写入文件,并在文件开头添加Unicode头就行了。
读文件时,VB并不判断读入的数据开头两个字节是否为 &HFF 和&HFE,它将读入的数据都自动转变为Unicode编码的字符串,如果原来就是Unicode编码的文本,哈哈,这下子就弄得一踏糊涂了。所以我们在读文件的时候,不能用通常的字符读入法,而要用二进制读入法,先将数据读入到一个Byte型的数组中,再判断头两个字节是否 &HFF 和 &HFE,如果是,就把这个数组直接赋值给一个字符串变量,再去掉该字符串的第一个字符;如果不是,就通过StrConv函数转换即可。


2.UTF_8编码文件的读写技术简述
下面我先简单讲述一下UTF-8编码的有关知识,并且只讲与我编写的读写UTF-8文本源代码有关的知识(如有谬误之处请各位兄弟指正),详细的理论请各位自己找专业文章看看。
UTF-8编码字符理论上可以最多到6个字节长,但目前全世界的所有文字和符号种类加起来也只要编到4个字节长就够了。
UTF-8是以8位(即1个字节)为单元对原始码进行编码(注意一点:这里所讲的原始码都是指Unicode码),并规定:多字节码(2个字节以上才称为多字节)以转换后第1个字节起头的连续“1”的数目(这些连续“1”称为标记位),表示转换成几个字节:“110”连续两个“1”,表示转换结果为2个字节,“1110”表示3个字节,而“11110”则表示4个字节……跟随在标记位之后的“0”称为分隔位,其作用是分隔标记位和字符码位。第2~第4个字节的起头两个位固定设置为“10”,也作为标记,剩下的6个位才做为字符码位使用。
这样,2字节UTF-8码剩下11个字符码位,可用以转换0080~07FF的原始字符码,3字节剩下16个字符码位,可用以转换0800~FFFF的原始字符码,由此类推。编码方式的模板如下:

原始码(16进制) UTF-8编码(二进制)
--------------------------------------------
0000 - 007F       0xxxxxxx
0080 - 07FF       110xxxxx 10xxxxxx
0800 - FFFF       1110xxxx 10xxxxxx 10xxxxxx
……
--------------------------------------------

模板中的“x”表示字符码。
VB能识别的Ascii码<007F,所以在VB中,Ascii码都只能编为1个字节的UTF-8码。Unicode编码在0080-07FF的范围内,被编为2字节的UTF-8码,其中0080-00FF的编码VB不能显示,0100-07FF的编码是少数国家的文字,与汉字无关,所以,我们基本上不会遇到2字节的UTF-8编码。汉字的Unicode编码范围为0800-FFFF,所以被编为3字节的UTF-8码。
例如“汉”字的Unicode编码是6C49,6C49在0800-FFFF之间,所以要用3个字节的模板:

1110xxxx 10xxxxxx 10xxxxxx。

UTF-8编码文件有UTF8头,用 &HEF、&HBB 和 &HBF 3个字节标记。
要写入UTF-8编码的文本文件,关键是对汉字编码的处理。我们从上述的汉字编码模板就可以看出,对汉字的处理步骤大致为:


第一步:取得汉字的Unicode码。
第二步:将Unicode码分解为2个16进制数据。
第三步:将这2个16进制数据转换成二进制数据并连接。
第四步:将二进制数据分解为3个串,第1个串为4个位,在前面加上标记位“1110”,第2、3个串均为6个位,分别在前面加上“10”标记位。
第五步:将这3个串分别转换为10进制数据并赋值给字节型数组。
第六步:将字节型数组用二进制法写盘,并且要先写入UTF8头(&HEF、&HBB、&HBF),再将转换好的数据写入。


要读取UTF-8编码的文本文件,对汉字的处理步骤大致为:


第一步:用二进制法读入文本数据,赋值给字节型数组,并判断前3个字节是否UTF8头,如果是,才进行以下的处理。
第二步:逐个字节判断是否汉字编码,如果是,就再提取后2个数组元素,共3个数组元素来加以处理。
第三步:将这3个数据都转换成16进制数据。
第四步:将3个16进制数据都转换成二进制数据。
第五步:从第1个二进制数据中去掉前4位,从第2、3个二进制数据中分别去掉前2位,并将这3个处理后的二进制数据依次连接,成为1个16位的字串。
第六步:从这个二进制串中分别提取前8位和后8位转换成2个10进制数据,这2个数据就是汉字的Unicode码了,将它们赋值给1个字符型变量即可。


由于上述的二进制数字均须进行大量的字符串操作,速度较慢,因此在实际的代码中,笔者采用了逻辑运算(位操作)来代替上述的字符串操作。
新建一个工程,在窗体上添加3个按纽,1个文本框,文本框设置成可接受多行文本。
代码如下:


Option Explicit


Private Sub Command1_Click()
On Error GoTo InErr
Dim fName As String, st As String, Dat() As Byte
Dim Dlg As Object
Set Dlg = CreateObject("MSComDlg.CommonDialog")
With Dlg
  .DialogTitle = "打开"
  .Flags = &H200C
  .CancelError = True
  .Filter = "普通文本方式打开|*.txt|Unicode方式打开|*.txt|UTF-8方式打开|*.txt"
  .ShowOpen
  fName = .FileName
End With

ReDim Dat(FileLen(fName) - 1) As Byte
Open fName For Binary As #1
Get #1, , Dat
Close #1

Select Case Dlg.FilterIndex '根据用户选取的打开方式来进行相应的操作
  Case 2 '如果是Unicode方式打开
    st = Dat
    If Dat(0) = &HFF And Dat(1) = &HFE Then st = Mid(st, 2) '如果有Unicode头
  Case 3 '如果是UTF8方式打开
    If Dat(0) = &HEF And Dat(1) = &HBB And Dat(2) = &HBF Then '如果有UTF8头
      st = UTFtoGB(3, Dat)
    Else '如果没有UTF8头
      st = UTFtoGB(0, Dat)
    End If
  Case 1 '如果是普通文本方式打开
    st = StrConv(Dat, vbUnicode)
End Select

Text1.Text = st
InErr:
End Sub


Private Function UTFtoGB(k As Long, Dat0() As Byte) As String '参数1-起始位置;参数2-UTF8数据
Dim L As Long, i As Long, j As Long, Dat1() As Byte
j = UBound(Dat0)
ReDim Dat1(j * 2)

For i = k To j
  If Dat0(i) > 223 And Dat0(i) < 240 Then '如果是3字节UTF-8编码
    Dat1(L + 1) = ((Dat0(i) And 15) * 16 + (Dat0(i + 1) And 60) / 4)
    Dat1(L) = (Dat0(i + 1) And 3) * 64 + (Dat0(i + 2) And 63)
    i = i + 2
  Else '如果是Ascii字符或其它UTF-8编码
    Dat1(L) = Dat0(i)
  End If
  L = L + 2
Next
 
ReDim Preserve Dat1(L - 1) '删除未用的数组元素
UTFtoGB = Dat1
End Function


Private Sub Command2_Click()
Dim Dat() As Byte
Dat = StrConv(Text1, vbFromUnicode)
Text1 = UTFtoGB(0, Dat)
End Sub


Private Sub Command3_Click()
On Error GoTo OutError
Dim Dat() As Byte, Dat1() As Byte, fName As String
Dim Dlg As Object
Set Dlg = CreateObject("MSComDlg.CommonDialog")

With Dlg
  .Filter = "保存为普通文本文件|*.txt|保存为Unicode文件|*.txt|保存为UTF-8文件|*.txt"
  .Flags = &H200A
  .DialogTitle = "另存为"
  .ShowSave
  fName = .FileName
End With

Select Case Dlg.FilterIndex '根据用户选取的保存类型来进行相应的操作
  Case 1 '保存为普通的TXT文件
    Dat = StrConv(Text1.Text, vbFromUnicode)
  Case 2 '保存为Unicode编码文件
    ReDim Dat1(1) As Byte
    Dat1(0) = &HFF: Dat1(1) = &HFE
    Dat = Text1.Text
  Case 3 '保存为UTF_8编码文件
    Dim L As Long, k As Long, i As Long
    Dat1 = Text1.Text: k = UBound(Dat1): ReDim Dat(k * 3)
    For i = 0 To k - 1 Step 2
      If Dat1(i + 1) = 0 Then '如果是Ascii字符
        Dat(L) = Dat1(i): L = L + 1 '字节计数+1
      Else
        Dat(L) = (Dat1(i + 1) And 240) / 16 Or 224
        Dat(L + 1) = (Dat1(i + 1) And 15) * 4 + ((Dat1(i) And 192) / 64) Or 128
        Dat(L + 2) = Dat1(i) And 63 Or 128
        L = L + 3
      End If
    Next
    ReDim Preserve Dat(L - 1) As Byte '删除未用的数组元素
    ReDim Dat1(2)
    Dat1(0) = &HEF: Dat1(1) = &HBB: Dat1(2) = &HBF
End Select

Open fName For Binary As #1
If Dlg.FilterIndex > 1 Then Put #1, , Dat1
Put #1, , Dat
OutError:
Close
End Sub


简要说明:
在UTFtoGB函数中,我对2字节的UTF-8编码未作判断处理,因为不会遇到,如果你有需求,请自行设计代码。
点击按纽1是读入文本文件,包括普通的文本文件和Unicode编码文件以及UTF-8编码文件。
点击按纽2是将Text1的UTF-8字符转换为VB可以正常显示的字符,例如在分析一些网页代码时,经常会看到UTF8字符,你可以将其复制到Text1中,再点击按纽2进行转换。但会有个别字符变成了问号,这是因为当字符串复制到剪切板上时,剪切板把不能识别的编码统统作问号处理。
点击按纽3将Text1的文本保存为文件,你可以选择保存为普通的文本文件,也可以保存为Unicode编码文件或者UTF-8编码文件。
另外,你也许还会遇到一列包含“%”的16进制字符,这其实也是UTF-8数列,例如:


%E9%83%BD%E5%B8%82%E6%83%85%E7%B7%A3%E6%98%9F%E5%BA%A7


怎么办?不要紧,再在窗体上添加第4个按纽,编写代码对其进行转换。运行后,把这个数列复制到文本框,点击按纽4即可:


Private Sub Command4_Click()
Dim st As String, z() As String, Dat() As Byte, i As Integer, j As Integer
st = Replace(Text1, "%", "%&H")
z = Split(st, "%")
j = UBound(z)
ReDim Dat(j)
For i = 0 To j: Dat(i) = Val(z(i)): Next
Text1 = UTFtoGB(1, Dat)
End Sub

回复列表 (共1个回复)

沙发

补充:
上文中提到的Unicode编码实际上也有2种方式,一种是常用的“小头方式”(Little endian),又称低位在前,我的源代码就是基于小头方式编写的;一种是不常用的“大头方式””(Big endian),又称高位在前,系统自带的记事本保存项目就有Big endian。它们的区别在于:2个编码数据的字节序不同,通俗地说,就是字节的排列位置不同。例如“A”的2个Unicode编码数据,小头方式的排列为&H41、&H00,大头方式的排列为&H00、&H41。用大头方式保存的Unicode文本文件,其头部标识为&HFE、&HFF,标识的字节序也与小头方式相反。
为了打开系统记事本以Big endian方式保存的文本,就要另外编写源代码,以下代码供参考。


Dim DAT() As Byte
ReDim DAT(FileLen(fName) - 1)
Open fName For Binary As #1
Get #1, , DAT
Close #1
If DAT(0) = &HFE And DAT(1) = &HFF Then '如果是Unicode big endian(大头方式)
  Dim Dat1() As Byte, i As Long, j As Long
  j = UBound(DAT)
  ReDim Dat1(j)
  For i = 0 To j - 1 Step 2: Dat1(i) = DAT(i + 1): Dat1(i + 1) = DAT(i): Next
  st = Dat1
  Text1 = Mid$(st, 2)
End If


作业题1:

请把这段代码加入到上面的代码中去。


作业题2:

自己设计保存为Unicode big endian编码文件的代码,也加入到上面的代码中去。

我来回复

您尚未登录,请登录后再回复。点此登录或注册