设为首页收藏本站Access中国

Office中国论坛/Access中国论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

返回列表 发新帖
查看: 2303|回复: 1
打印 上一主题 下一主题

[模块/函数] 大文本、定宽、中英文混编的UTF-8文本导入方案

[复制链接]
跳转到指定楼层
1#
发表于 2012-12-23 10:11:56 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 duzili 于 2012-12-23 10:17 编辑

注意标题,是定宽的,是中英文混编的,而且是大文本--最少数十兆吧。如果没有这三限定条件,utf-8文本导入数据库并不是什么难事,我也不用抓狂。

----------------------------------------------------------------------------------------------------------------------------------------------------------
如果你的文本是定宽文本,而且是中英文混编,转换之后你将发现数据长度发生变化了...这是因为,UTF-8使用一至四个字节为每个字符编码,如果要转换定宽文本,首先你要知道文本的具体编码。不过大多数情况下,utf-8编码都是三个字节来存储中文。
对于access或sql server来说,UTF-8文本支持的并不好,而fox pro或oracle及其他数据库对utf-8支持的很好。在sql server中,如果手动导入utf-8文本,没问题,它可以正确识别。但要是用bcp工具或t-sql导入,抱歉,sql server明确告诉你不支持。而在access中,导入后会发生错位!--它把中文当做两个字符处理...
  
首先解决Utf-8转换的问题。
很多人会想到使用  ADODB.Stream   Microsoft ActiveX Data Objects 2.5  )进行转换,但是如果这个文本文件很大,比如说20M,   其速度将是很难容忍。  所幸微软提供了一个api函数MultiByteToWideChar ,其速度还令人满意。

然后解决3个字节的问题。
我对utf-8文本的编码没有太多研究,也没精力和能力去研究,所以复杂问题简单化,每个GBK编码汉字后面加个空格不三个字节吗!
循环读取文本文件的每一行,判断每个字符的ascii码,如果小于0,则是非ascii文字,就给这个字符后面加一个空格,最后再拼接成完整的一行...写入....完成。举例来说,对“101001现金001        123412345”这样的文本,最后将变成‘101001现 金 001        123412345 ’。尽管每个汉字后面都有个空格,感觉挺怪异,但是好像是我能够想到的最好办法了....

在我的方法中,UTF-8转换是在内存中进行的,这是速度的关键所在。 所以,我没有采用逐行读取文本文件的模式,而是采用块读取的模式。即按照1024的整数倍(倍数根据内存大小设置),将文本读入到内存中,然后进行UTF-8转换。本来想将汉字加空格也放在内存中进行,但好像速度提升不明显。经过测试,这个速度还是很快,数百兆的文本文件,不到一分钟就转换完毕--当然我用的是服务器......

这个代码是我在2008年写的,当时我在澳门。当时时间紧迫,现在回过头来看,变量定义都在偷懒,连 Dim iii As Long 这样的定义都写出来了,汗颜啊!

弹指一挥间,尼玛已经四年了。

另外,对utf-8文本导入到微软数据库的问题,若有大侠有更好的解决方法,烦请告知。我一度想放弃sql server和access,要不是老了学不动了,早就当叛徒投降oracle。不过说实话,oracle的性能还是牛哄哄的


以下是代码,一共三个函数:
utf8Filetransfor  转换UTf-8文本,进行文本块分割。
Utf8ToUnicode 将utf-8文本转换为GBK。转换函数使用了api函数MultiByteToWideChar
writeGBKtext     将每个中文字符后面加上空格,并写入到新文本文件中

调用方法:
utf8Filetransfor(utf8file As String, newtextfile As String)
  1. Private Declare Function MultiByteToWideChar Lib "kernel32" (ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As Long, ByVal cchMultiByte As Long, ByVal lpWideCharStr As Long, ByVal cchWideChar As Long) As Long
  2. Private Const CP_UTF8 = 65001

  3. Function Utf8ToUnicode(ByRef Utf() As Byte) As String
  4. Dim msg As String
  5.     Dim lRet As Long
  6.     Dim lLength As Long
  7.     Dim lBufferSize As Long
  8.     lLength = UBound(Utf) - LBound(Utf) + 1
  9.     If lLength <= 0 Then Exit Function
  10.     lBufferSize = lLength * 2
  11.     Utf8ToUnicode = String$(lBufferSize, Chr(0))
  12.     lRet = MultiByteToWideChar(CP_UTF8, 0, VarPtr(Utf(0)), lLength, StrPtr(Utf8ToUnicode), lBufferSize)
  13.     If lRet <> 0 Then
  14.         Utf8ToUnicode = Left(Utf8ToUnicode, lRet)
  15.     End If
  16.     Exit Function
  17. End Function




  18. Sub utf8Filetransfor(utf8file As String, newtextfile As String)
  19. 'On Error GoTo errexit:

  20.     DoEvents
  21.     Dim f_txtstream As TextStream
  22.     Dim fso As New FileSystemObject, fle As File
  23. Dim Stg_read As String
  24. Dim iii As Long
  25. iii = 1
  26.     Dim sfFileName As String    '文件名
  27.     Dim sReadData As String
  28.     Dim Fn As Long
  29.     Dim lFileLong As Double    '存储文件大小,如果文件大小超过double值的范围,就会出错
  30.     Dim lReadL As Double    '存储下一读取位置,如果超过double值的范围,就会出错
  31.     Dim LreadNow As Double
  32.     Dim utfbyte() As Byte    '存储读取出来的字节
  33.     Dim DAT() As Byte, DAT1() As Byte
  34.     Dim ii As Long     '循环变量
  35.     Dim Sutf As String, s_tmp As String
  36.     sfFileName = utf8file


  37.     LreadNow = 1    '已经读写完毕的文件位置,初始为1
  38.     Dim lng_Split As Long    '定义分割的块数

  39.     b = 3
  40.     lReadL = 1024 * b '每次读入数据的长度字节长度,除以整行字节数后,余数不要太大,因为后面要进行行结束符的判断
  41.     lng_Split = 1

  42.     Fn = FreeFile

  43.     If Dir(newtextfile) <> "" Then Kill newtextfile
  44.     fso.CreateTextFile newtextfile
  45.     Set fle = fso.GetFile(newtextfile)
  46.     Set f_txtstream = fle.OpenAsTextStream(ForWriting)

  47.     Open sfFileName For Binary As #Fn '以二进制打开文本文件
  48.     lFileLong = LOF(Fn)    '取得文件的大小
  49.     If lFileLong < lReadL Then lReadL = lFileLong    '如果设定的每次读入长度小于文件长度,则每次读入长度等于文件长度




  50.     Do While LreadNow < lFileLong

  51.         ReDim DAT(lReadL), DAT1(1)    '分配内存空间.分配n*1024较好,速度会更快

  52.         DoEvents
  53.         Seek #Fn, LreadNow    '在 Open 语句打开的文件中,设置下一个读/写操作的位置
  54.         Get #Fn, , DAT   '  *读取等于分割大小的二进制数据到内存数组*/

  55.         '-----以下代码判断是否读取的是整行-------------------
  56.         If DAT(lReadL) <> &HD Then
  57.             For ii = 1 To lReadL
  58.                 If DAT(lReadL - ii) = &HD Then '判断是否是整行

  59.                     lReadL = lReadL - ii


  60.                     ReDim DAT(lReadL), DAT1(1)    '分配内存空间.分配n*1024较好,速度会更快
  61.                     Seek #Fn, LreadNow    '在 Open 语句打开的文件中,设置下一个读/写操作的位置
  62.                     Get #Fn, , DAT   '  *读取等于分割大小的二进制数据到内存数组*/

  63.                     Exit For

  64.                 End If
  65.             Next

  66.         End If
  67.         LreadNow = LreadNow + lReadL + 1
  68.         l1.Caption = Format(Int(LreadNow / 1024), "##,###") & "kb"
  69.         '----------over------------------------------------'
  70.         Stg_read = ""
  71. Stg_read = Utf8ToUnicode(DAT)
  72. 'Debug.Print Stg_read
  73.      f_txtstream.Write Stg_read

  74.         '-=====不使用open方法的原因是,每一次使用Print之后,会产生一个空行================
  75.       '   Open newtextfile For Append As 2#    '打开新文件,如果不存在,则创建一个
  76.       '        Write #2, Stg_read       '写入文本
  77.               
  78.      '    Close 2#
  79.         '=============================================================
  80.         lReadL = 2500 * b '每次读入数据的长度字节长度,除以整行字节数后,余数不要太大,因为后面要进行行结束符的判断

  81.         If lReadL >= lFileLong Or LreadNow = lFileLong Then Exit Do    '如果块大小等于文件大小,不用循环,直接退出

  82.         If LreadNow > lFileLong Then lFileLong = lFileLong - LreadNow



  83.     Loop
  84.     Close #Fn
  85.     f_txtstream.Close

  86.     Set f_txtstream = Nothing
  87.     Set fle = Nothing




  88.     Set fso = Nothing
  89. writeGBKtext utf8file & "bak", utf8file
  90. If Command = "" Or IsNull(Command) = True Then MsgBox "txtfile trim successful!"
  91. Exit Sub
  92. errexit:
  93. If Err = 5 Then msg = "maybe have two reasons:" & Chr(10) & "1.This textfile's code not is Utf-8" & Chr(10) & "2.This textfile include  East Asian language's words,but your computer not setup it"

  94.    
  95. If Command = "" Or IsNull(Command) = True Then MsgBox "error :" & Err.Number & "," & Err.Description & Chr(10) & msg, vbCritical, "err"

  96. End Sub




  97. Sub writeGBKtext(gbkfileName As String, UTFflileNAme As String) '文本整理

  98. Dim fso As New FileSystemObject
  99. Dim jj As Long
  100.     Dim ss_line As String, ss_new As String, ss_mid As String '存储旧的gbk单行文本,存储新的一行文本,存储单个文本
  101.     Dim ii As Integer, int_txtlong As Integer '存储单行文件循环变量

  102. jj = 1

  103.     If Dir(gbkfileName & "x") <> "" Then Kill gbkfileName & "x" '判断新文件是否存在,如果存在则删除
  104.     Open gbkfileName & "x" For Append As 2# '打开新文件,如果不存在,则创建一个



  105.     Open gbkfileName For Input As 1# '打开已经transfor为gbk编码的文本文件

  106.     Do Until EOF(1) 'gbk文件循环开始
  107.     DoEvents
  108.         Line Input #1, ss_line '读入一行到ss_line
  109.         ss_new = "" 'ss_new赋值为空
  110.         ss_mid = "" 'ss_mid

  111.         int_txtlong = Len(ss_line)
  112.         For ii = 1 To int_txtlong '从第一个字符开始循环,取得每一个字的ascii码
  113.             ss_mid = Mid(ss_line, ii, 1) '取得一个字
  114.             If Asc(ss_mid) < 0 Then '判断字符asc码,如果小于0,则是非ascii文字
  115.                 ss_new = ss_new & ss_mid & " "
  116.             Else
  117.                 ss_new = ss_new & ss_mid
  118.             End If
  119.         Next

  120.         Print #2, ss_new '写入文本
  121.         jj = jj + 1
  122.          ' l1.Caption = Format(Int((jj * int_txtlong) / 1024), "##,###") & "kb" '计算的不准确,将就一下
  123.       
  124.     Loop

  125.     Close #1
  126.     Close 2#
  127. l1.Caption = ""
  128. fso.DeleteFile UTFflileNAme
  129. fso.DeleteFile gbkfileName
  130. Name gbkfileName & "x" As UTFflileNAme
  131. Set fso = Nothing
  132. End Sub
复制代码
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享分享 分享淘帖 订阅订阅

点击这里给我发消息

2#
发表于 2012-12-23 12:06:03 | 只看该作者
坐个沙发
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|站长邮箱|小黑屋|手机版|Office中国/Access中国 ( 粤ICP备10043721号-1 )  

GMT+8, 2024-5-4 02:45 , Processed in 0.072584 second(s), 25 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表