打开、保存Unicode或UFT-8编码文件的源代码
vb吧
全部回复
仅看楼主
level 4
601居士 楼主
打开、保存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
2021年12月30日 03点12分 1
level 11
赞!
2021年12月30日 03点12分 2
level 15
不错,原理介绍很详细,方法也很好,不过打开文件的时候不应该让用户选择文件的编码类型吧,应该让程序自行判断才是
这里想说点题外话,是关于字节型(Byte)数组的,就是它在多数场合是可以跟字符串类型(String)通用的,能用String的地方,也可以用Byte数组,并且两种类型之间也可以直接相互赋值(这一点你也提到了)
2021年12月30日 03点12分 3
level 4
601居士 楼主
打开文件时让程序自动判断编码方式,从而采用相应的代码,这个问题很容易解决,去掉源代码中的 Select Case 语句的有关代码,直接用 If 来判断就行了,这个就作为家庭作业吧。
补充:
  上文中提到的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
如何将这段代码加入到上面的源代码中去,就作为家庭作业的第二题吧。
2022年01月02日 02点01分 4
level 9
很好,不用API,自己转换。
不过对于没有文件头的utf文档,判断并不容易。
2023年04月21日 06点04分 7
1