主题:[讨论]判断文件编码方式是否是UTF-8及正确读取的问题
天天学习
[专家分:4570] 发布于 2010-06-21 11:43:00
不知道大家有没有遇到过“明明在记事本中显示的是utf-8格式的文件,但却没有BOM头,前3个字节也不是&HEF、&HBB 和 &HBF,甚至也不是&HFF 和 &HFE,而直接是文件的内容(很像ANSI)”的情况。
最近就遇到这么个utf-8文件,怎么检测编码方式都无法检测到是utf-8,但是用utf-8的方式来读取才能得到不是乱码的内容,很让人头疼。
按照一江秋水的帖子 http://bbs.pfan.cn/post-250830.html中的直接检测文件前三个字节内容来判断判断不出来,用 http://topic.csdn.net/u/20090623/16/4149E2C9-28B8-4AC1-9E2F-F534EF9BFDEA.html 中3楼的代码也检测不出是UTF-8。
代码就不再贴了
附上这个诡异的文件,供大家研究。
回复列表 (共10个回复)
沙发
天天学习 [专家分:4570] 发布于 2010-06-21 12:05:00
算了,还是附上部分代码,方便大家帮忙测试下
[code=c]
'直接检测文件前三个字节内容来判断是否是UTF-8
Private Function IsUTF8(ByVal strFile As String) As Boolean
Dim fNo As Integer
Dim bytTmp(2) As Byte
fNo = FreeFile()
Open strFile For Binary As fNo
Get #1, 1, bytTmp()
Close fNo
Debug.Print Hex(bytTmp(0)), Hex(bytTmp(1)), Hex(bytTmp(2))
If bytTmp(0) = &HFF And bytTmp(1) = &HFE Then
'unicode编码
IsUTF8 = False
ElseIf bytTmp(0) = &HEF And bytTmp(1) = &HBB And bytTmp(2) = &HBF Then
IsUTF8 = True
End If
End Function
[/code]
[code=c]
Option Explicit
'mduUnicodeEncodeFormat.bas
'模块:UTF文本文件访问
'作者:zyl910
'版本:1.0
'日期:2006-1-23
'== 说明 ===================================================
'支持Unicode编码的文本文件读写。暂时支持ANSI、UTF-8、UTF-16LE、UTF-16BE这几种编码文本
'== 更新记录 ===============================================
'[V1.0] 2006-1-23
'1.支持最常见的ANSI、UTF-8、UTF-16LE、UTF-16BE这几种编码文本
'## 编译预处理常数 #########################################
'== 全局常数 ===============================================
'IncludeAPILib:引用了API库,此时不需要手动写API声明
'## API ####################################################
#If IncludeAPILib = 0 Then
'== File ===================================================
Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, ByVal lpOverlapped As Long) As Long
Private Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten As Long, ByVal lpOverlapped As Long) As Long
Private Declare Function GetFileSize Lib "kernel32" (ByVal hFile As Long, lpFileSizeHigh As Long) As Long
Private Declare Function SetFilePointer Lib "kernel32" (ByVal hFile As Long, ByVal lDistanceToMove As Long, lpDistanceToMoveHigh As Long, ByVal dwMoveMethod As Long) As Long
Private Const INVALID_HANDLE_VALUE = -1
Private Const GENERIC_READ = &H80000000
Private Const GENERIC_WRITE = &H40000000
Private Const FILE_SHARE_READ = &H1
Private Const FILE_SHARE_WRITE = &H2
Private Const Create_NEW = 1
Private Const Create_ALWAYS = 2
Private Const OPEN_EXISTING = 3
Private Const OPEN_ALWAYS = 4
Private Const TRUNCATE_EXISTING = 5
Private Const FILE_ATTRIBUTE_NORMAL = &H80
Private Const FILE_BEGIN = 0
Private Const FILE_CURRENT = 1
Private Const FILE_END = 2
'== Unicode ================================================
Private Declare Function MultiByteToWideChar Lib "kernel32" (ByVal CodePage As Long, ByVal dwFlags As Long, ByRef lpMultiByteStr As Any, ByVal cchMultiByte As Long, ByRef lpWideCharStr As Any, ByVal cchWideChar As Long) As Long
Private Declare Function WideCharToMultiByte Lib "kernel32" (ByVal CodePage As Long, ByVal dwFlags As Long, ByRef lpWideCharStr As Any, ByVal cchWideChar As Long, ByRef lpMultiByteStr As Any, ByVal cchMultiByte As Long, ByRef lpDefaultChar As Any, ByVal lpUsedDefaultChar As Long) As Long
Private Const CP_UTF8 As Long = 65001
#End If
'###########################################################
'Unicode编码格式
Public Enum UnicodeEncodeFormat
UEF_ANSI = 0 'ANSI+DBCS
UEF_UTF8 'UTF-8
UEF_UTF16LE 'UTF-16LE
UEF_UTF16BE 'UTF-16BE
UEF_UTF32LE 'UTF-32LE
UEF_UTF32BE 'UTF-32BE
UEF_Auto = -1 '自动识别编码
'隐藏项目
[_UEF_Min] = UEF_ANSI
[_UEF_Max] = UEF_UTF32BE
End Enum
'ANSI+DBCS方式的文本所使用的代码页。默认为0,表示使用系统当前代码页。可以利用该参数实现读取其他代码编码的文本,比如想在 简体中文平台下 读取 繁体中文平台生成的txt,就将它设为950
Public UEFCodePage As Long
[/code]
板凳
天天学习 [专家分:4570] 发布于 2010-06-21 12:38:00
[code=c]
'判断BOM
'返回值:BOM所占字节
'dwFirst:[in]文件最开始的4个字节
'fmt:[out]返回编码类型
Private Function UEFCheckBOM(ByVal dwFirst As Long, ByRef fmt As UnicodeEncodeFormat) As Long
If dwFirst = &HFEFF& Then
fmt = UEF_UTF32LE
UEFCheckBOM = 4
ElseIf dwFirst = &HFFFE0000 Then
fmt = UEF_UTF32BE
UEFCheckBOM = 4
ElseIf (dwFirst And &HFFFF&) = &HFEFF& Then
fmt = UEF_UTF16LE
UEFCheckBOM = 2
ElseIf (dwFirst And &HFFFF&) = &HFFFE& Then
fmt = UEF_UTF16BE
UEFCheckBOM = 2
ElseIf (dwFirst And &HFFFFFF) = &HBFBBEF Then
fmt = UEF_UTF8
UEFCheckBOM = 3
Else
fmt = UEF_ANSI
UEFCheckBOM = 0
End If
End Function
'生成BOM
'返回值:BOM所占字节
'fmt:[in]编码类型
'dwFirst:[out]文件最开始的4个字节
Private Function UEFMakeBOM(ByVal fmt As UnicodeEncodeFormat, ByRef dwFirst As Long) As Long
Select Case fmt
Case UEF_UTF8
dwFirst = &HBFBBEF
UEFMakeBOM = 3
Case UEF_UTF16LE
dwFirst = &HFEFF&
UEFMakeBOM = 2
Case UEF_UTF16BE
dwFirst = &HFFFE&
UEFMakeBOM = 2
Case UEF_UTF32LE
dwFirst = &HFEFF&
UEFMakeBOM = 4
Case UEF_UTF32BE
dwFirst = &HFFFE0000
UEFMakeBOM = 4
Case Else
dwFirst = 0
UEFMakeBOM = 0
End Select
End Function
'判断文本文件的编码类型
'返回值:编码类型。文件无法打开时,返回UEF_Auto
'FileName:文件名
Public Function UEFCheckTextFileFormat(ByVal FileName As String) As UnicodeEncodeFormat
Dim hFile As Long
Dim dwFirst As Long
Dim nNumRead As Long
'打开文件
hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_WRITE, ByVal 0&, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, ByVal 0&)
If INVALID_HANDLE_VALUE = hFile Then '文件无法打开
UEFCheckTextFileFormat = UEF_Auto
Exit Function
End If
'判断BOM
dwFirst = 0
Call ReadFile(hFile, dwFirst, 4, nNumRead, ByVal 0&)
nNumRead = UEFCheckBOM(dwFirst, UEFCheckTextFileFormat)
'Debug.Print nNumRead
'关闭文件
Call CloseHandle(hFile)
End Function
'读取文本文件
'返回值:读取的文本。返回vbNullString表示文件无法打开
'FileName:[in]文件名
'fmt:[in,out]使用何种文本编码格式来读取文本。为UEF_Auto时表示自动判断,且在fmt参数返回文本所用编码格式
Public Function UEFLoadTextFile(ByVal FileName As String, Optional ByRef fmt As UnicodeEncodeFormat = UEF_Auto) As String
Dim hFile As Long
Dim nFileSize As Long
Dim nNumRead As Long
Dim dwFirst As Long
Dim CurFmt As UnicodeEncodeFormat
Dim cbBOM As Long
Dim cbTextData As Long
Dim CurCP As Long
Dim byBuf() As Byte
Dim cchStr As Long
Dim i As Long
Dim byTemp As Byte
'判断fmt范围
If fmt <> UEF_Auto Then
If fmt < [_UEF_Min] Or fmt > [_UEF_Max] Then
GoTo FunEnd
End If
End If
'打开文件
hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_WRITE, ByVal 0&, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, ByVal 0&)
If INVALID_HANDLE_VALUE = hFile Then '文件无法打开
GoTo FunEnd
End If
'判断文件大小
nFileSize = GetFileSize(hFile, nNumRead)
If nNumRead <> 0 Then '超过4GB
GoTo FreeHandle
End If
If nFileSize < 0 Then '超过2GB
GoTo FreeHandle
End If
[/code]
3 楼
天天学习 [专家分:4570] 发布于 2010-06-21 12:39:00
[code=c]
'判断BOM
dwFirst = 0
Call ReadFile(hFile, dwFirst, 4, nNumRead, ByVal 0&)
cbBOM = UEFCheckBOM(dwFirst, CurFmt)
'恢复文件指针
If fmt = UEF_Auto Then '自动判断
fmt = CurFmt
'cbBOM = cbBOM
Else '手动设置编码
If fmt = CurFmt Then '若编码相同,则忽略BOM标记
'cbBOM = cbBOM
Else '编码不同,那么都是数据
cbBOM = 0
End If
End If
Call SetFilePointer(hFile, cbBOM, ByVal 0&, FILE_BEGIN)
cbTextData = nFileSize - cbBOM
'读取数据
UEFLoadTextFile = ""
Select Case fmt
Case UEF_ANSI, UEF_UTF8
'判断应使用的CodePage
CurCP = IIf(fmt = UEF_UTF8, CP_UTF8, UEFCodePage)
'分配缓冲区
On Error GoTo FreeHandle
ReDim byBuf(0 To cbTextData - 1)
On Error GoTo 0
'读取数据
nNumRead = 0
Call ReadFile(hFile, byBuf(0), cbTextData, nNumRead, ByVal 0&)
'取得Unicode文本长度
cchStr = MultiByteToWideChar(CurCP, 0, byBuf(0), nNumRead, ByVal 0&, ByVal 0&)
If cchStr > 0 Then
'分配字符串空间
On Error GoTo FreeHandle
UEFLoadTextFile = String$(cchStr, 0)
On Error GoTo 0
'取得文本
cchStr = MultiByteToWideChar(CurCP, 0, byBuf(0), nNumRead, ByVal StrPtr(UEFLoadTextFile), cchStr + 1)
End If
Case UEF_UTF16LE
cchStr = (cbTextData + 1) \ 2
'分配字符串空间
On Error GoTo FreeHandle
UEFLoadTextFile = String$(cchStr, 0)
On Error GoTo 0
'取得文本
nNumRead = 0
Call ReadFile(hFile, ByVal StrPtr(UEFLoadTextFile), cbTextData, nNumRead, ByVal 0&)
'修正文本长度
cchStr = (nNumRead + 1) \ 2
If cchStr > 0 Then
If Len(UEFLoadTextFile) > cchStr Then
UEFLoadTextFile = Left$(UEFLoadTextFile, cchStr)
End If
Else
UEFLoadTextFile = ""
End If
Case UEF_UTF16BE
'分配缓冲区
On Error GoTo FreeHandle
ReDim byBuf(0 To cbTextData - 1)
On Error GoTo 0
'读取数据
nNumRead = 0
Call ReadFile(hFile, byBuf(0), cbTextData, nNumRead, ByVal 0&)
If nNumRead > 0 Then
'隔两字节翻转相邻字节
For i = 0 To nNumRead - 1 - 1 Step 2 '再-1是为了避免最后多出的那个字节
byTemp = byBuf(i)
byBuf(i) = byBuf(i + 1)
byBuf(i + 1) = byTemp
Next i
'取得文本
UEFLoadTextFile = byBuf 'VB允许String中的字符串数据与Byte数组直接转换
End If
Case UEF_UTF32LE
UEFLoadTextFile = vbNullString '暂时不支持
Case UEF_UTF32BE
UEFLoadTextFile = vbNullString '暂时不支持
Case Else
Debug.Assert False
End Select
FreeHandle:
'关闭文件
Call CloseHandle(hFile)
FunEnd:
End Function
[/code]
4 楼
一江秋水 [专家分:9680] 发布于 2010-06-21 18:27:00
这样的问题我也经常碰到,因为没有特征码,所以无法判断是否UTF-8编码,只能在发现装入的文本疑似UTF-8编码时,在窗口中进行转换
5 楼
天天学习 [专家分:4570] 发布于 2010-06-22 08:28:00
[quote]这样的问题我也经常碰到,因为没有特征码,所以无法判断是否UTF-8编码,只能在发现装入的文本疑似UTF-8编码时,在窗口中进行转换[/quote]
怎么“发现装入的文本疑似UTF-8编码” ?一个一个地检查是不是乱码?
6 楼
merry05 [专家分:8920] 发布于 2010-06-22 10:28:00
http://tonylian.javaeye.com/blog/411558
这篇可以参考
00000000 - 0000007F 0xxxxxxx
00000080 - 000007FF 110xxxxx 10xxxxxx
00000800 - 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
00010000 - 001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
没有BOM头的文件还是比较常见的,比如在写PHP时为了保证header能最先输出,通常都会将BOM头给人为去掉
7 楼
天天学习 [专家分:4570] 发布于 2010-06-22 11:06:00
参照merry05给的网址中的方法
[code=c]
Private Function IsTextUTF8(ByRef bytSrc() As Byte) As Boolean
On Error GoTo errHandler
Dim i As Long
Dim AscN As Long
Dim n As Long
n = UBound(bytSrc)
Do While i <= n
If bytSrc(i) < 128 Then
i = i + 1 'ascii字符
AscN = AscN + 1
ElseIf (bytSrc(i) And &HE0) = &HC0 And (bytSrc(i + 1) And &HC0) = &H80 Then
i = i + 2 '2个字节的utf8
ElseIf (bytSrc(i) And &HF0) = &HE0 And (bytSrc(i + 1) And &HC0) = &H80 And (bytSrc(i + 2) And &HC0) = &H80 Then
i = i + 3 '3个字节的utf8
Else
IsTextUTF8 = False
Exit Function
'Err.Raise ERROR_USER, , "未知编码"
End If
Loop
IsTextUTF8 = (AscN <> n)
Exit Function
errHandler:
Debug.Print Err.Number, Err.Description
#If DEBUG_MODE Then
Stop: Resume
#End If
IsTextUTF8 = False
End Function
[/code]
测试:
Dim fno As Integer
Dim strfile As String
Dim bytSrc() As Byte
strfile = "F:\焦圣华\VB.Workspace\AspEncode\testutf8.txt"
fno = FreeFile
Open strfile For Binary As fno
ReDim bytSrc(LOF(fno) - 1) As Byte
Get #fno, , bytSrc()
Close #fno
Debug.Print IsTextUTF8(bytSrc())
用这个没有BOM头的UTF-8文件、ANSI编码的文件、有BOM头的UTF-8文件测试,均能正常判断。
8 楼
joforn [专家分:1460] 发布于 2010-06-23 15:41:00
好多网页都是UTF8编码,但是文件头标识被去掉。
9 楼
一江秋水 [专家分:9680] 发布于 2010-06-24 08:02:00
LZ的代码没必要将整个文本测试完,只要测得其中有UTF-8编码即可退出
10 楼
天天学习 [专家分:4570] 发布于 2010-06-24 08:51:00
[quote]LZ的代码没必要将整个文本测试完,只要测得其中有UTF-8编码即可退出[/quote]
好主意。尚未测试
我来回复