2.3.8 Stream对象的用途
网络上对Stream进行详细讲解的文章是不多的,很多文章只是一些属性和方法的简单列举,更多的只是贴上一段代码,然后告诉你这样用就可以了。这样是学不到东西的,遇到新问题的时候,仍然是不知所措。
前文讲解的都是Stream操作的基础知识,希望大家能够多加练习,真正把它们弄透,达到随心所欲的地步,因为Stream对象实在是太有用了。
下面就从用途的角度出发,进行一下整理。为了代码的重复利用,这里使用Sub或Function的形式进行了封装,所有方法都在StreamFunction.asp文件中。
(1)按指定的字符集读取文件内容
Stream对象操作文本的一大优点就是它可以指定字符集,这是FSO无能为力的。读取文件和保存文件的例子上面都说过,不再多说,直接看方法。
'filePath:文件物理路径 'CharSet:文件的字符集 Function ReadTextFile(filePath, CharSet) Dim stream Set stream = Server.CreateObject("adodb.stream") stream.Type = 1 '二进制方式 stream.Open stream.LoadFromFile filePath '载入文件 stream.Type=2 '文本方式 stream.Charset = CharSet '设置字符集 ReadTextFile = stream.ReadText '读取文本 stream.Close Set stream = nothing End Function
使用该方法时,需要事先知道文件使用的编码,若设置错误的话,读取到的文字将是乱码。
(2)按指定的字符集保存内容到文件
对于Unicode编码,前面我们都是直接使用“unicode”来指定的,它对应的其实就是Unicode Little Endian,前缀是“FF FE”。实际上,还有一个Unicode Big Endian,它的前缀是“FE FF”。想显式指定,Unicode LE可以用“unicodeFFFE”, Unicode BE可以用“unicodeFEFF”。
这些前缀被称为BOM(byte order mark),即字节顺序标识。一些情况下,可能不想在文件开头写入BOM,可以通过数据流之间复制数据的办法来跳过BOM。
看一下实现的方法。
'filePath:文件物理路径 'fileContent:文件内容 'CharSet:文件的字符集 'isWriteBOM:是否写入BOM Sub WriteTextFile(filePath, fileContent, CharSet, isWriteBOM) Dim stream Set stream = Server.CreateObject("adodb.stream") stream.Type = 2 '文本方式 stream.Charset = CharSet stream.Open stream.WriteText fileContent '如果是Unicode或UTF8,并且不写入BOM,则特殊处理一下 If instr("unicode|unicodefffe|unicodefeff|utf-8", Lcase(CharSet))>0 and NOT is WriteBOM Then '创建另外一个Stream对象 Set streamNoBOM = Server.CreateObject("adodb.stream")
streamNoBOM.Type = 1 '二进制方式 streamNoBOM.Open '原Stream对象跳过BOM, UTF-8是3个字节,Unicode是2个字节 If Lcase(CharSet)="utf-8" Then stream.Position = 3 Else stream.Position = 2 End If '复制数据 stream.CopyTo streamNoBOM '写入文件 streamNoBOM.SaveToFile filePath,2 '文件存在则覆盖 streamNoBOM.Close set streamNoBOM = nothing Else stream.SaveToFile filePath,2 '文件存在则覆盖 End If stream.Close Set stream = nothing End Sub
(3)编码转换
首先,请不要试图在写入数据后变更Charset来实现编码转换,那只是错误的读取,并不是转换。
Stream对象间进行数据复制的例子,实际就是一个编码转换的例子。这种转换以Unicode为中介,将某种编码的字符,转换为另一种编码中相同字形的字符。目标字符集中可能不存在对应的字符,所以转换结果中可能存在问号(不存在的字符会以问号代替)。
要记住,目标编码的数据只存在于Stream对象内,得到它的原型的最好办法就是使用SaveToFile方法。所以,比较实用的一个转换方式就是,读入文件数据,变更编码,写回文件,从而实现文件编码的转换。当然,这种转换只适用于GBK、Big5、Shift_JIS等编码与Unicode或UTF-8之间的转换,而不适用于Big5与Shift_JIS之间的转换,因为二者不是包含关系,会有一些字符无法转换。
其实想一想,你就会发现,只要调用前面提供的ReadTextFile()和WriteTextFile()两个方法就能实现编码转换过程。所以,这里就不提供单独的方法了。
(4)二进制数据转换为文本
这里所说的二进制数据是指由文本转换过去的,并不说任意的二进制数据都可以。把一个图片的数据转换为文本没有什么意义,而且图片数据中可能包含0x00这个字符(这个字符通常作为字符串的结束符),会影响文本的读取。
看一下实现的方法。
'byteData:二进制数据 'CharSet:字符集 Function BinaryToText(byteData, CharSet) Dim stream Set stream = Server.CreateObject("ADODB.Stream") stream.Type = 1 '二进制方式 stream.Open stream.Write byteData '写入二进制数据 stream.Position = 0 stream.Type = 2 '变更为文本方式 stream.Charset = CharSet BinaryToText = stream.ReadText '读取文本 stream.Close Set stream = nothing End Function
(5)BSTR数据转换为文本
由于Stream对象的Write方法要求参数必须是真正的字节数组,所以在BinaryToText()方法中直接传入BSTR作为参数是不行的。那么,BSTR数据应该怎样转换为文本呢?下面就提供一种思路。
假设内存中有这样一段BSTR数据,“E698A5E79CA0E4B88DE8A789E69993”,它是“春眠不觉晓”几个字的UTF-8编码形式。
首先,以字节为单位,将每个字节转换为一个Unicode字符,那么“E6”将变为“E6 00”, “98”将变为“98 00”,以此类推,这些字符组成了新的字符串。然后,将此字符串写入Stream对象,设定CharSet为“iso-8859-1”,那么将发生Unicode编码到iso-8859-1编码的转换,“E6 00”变回了“E6”, “98 00”变回了“98”,以此类推。到此,我们已经将BSTR的数据写入了Stream对象,剩下的工作就简单了。将Stream对象的Charset变更为目标编码,这里设置为“UTF-8”,然后读取文本即可。
那么,为什么第二步中CharSet要使用“iso-8859-1”呢,因为它在128~255这个范围内有字符定义(因为BSTR数据可能包含汉字,所以一个字节可能大于128)。这一步如果使用ASCII、GBK或Big5等编码是不行的,这些编码在128~255范围内都没有字符定义,转换后字符会变成问号。
下面看一下范例。
BSTR2Text.asp
<%@codepage=936%> <% Response.Charset = "GBK"
'十六进制数据 Dim hexStr, bstr hexStr = "E698A5E79CA0E4B88DE8A789E69993" '拼接BSTR数据 For i=1 To Len(hexStr) Step 2 bstr = bstr & ChrB("&H" & Mid(hexStr, i,2)) Next response.write "BSTR的数据:" & getMemoryFormat(bstr) & "<br>" '进行转换 result = bstr2Text(bstr, "UTF-8") '输出转换结果 response.write result '---------------BSTR转换为文本------------------------ 'bstr:BSTR数据 'targetCharset:目标字符集 Function bstr2Text(bstr, targetCharset) '首先将BSTR数据转换为字符串。 Dim str str = "" For i = 1 To LenB(bstr) '将每个字节的数据转换为两个字节的字符。如E6将变为E6 00 '一定要使用ChrW(),不要使用Chr(),后者受当前CodePage影响 str = str & chrw(AscB(MidB(bstr, i,1))) Next response.write "内存的数据:" & getMemoryFormat(str) & "<br>" '将字符串写入Stream对象,实现Unicode到ISO 8859-1的转换 Dim stream Set stream = Server.CreateObject("adodb.stream") stream.Type = 2 '文本方式 stream.Charset = "iso-8859-1" '字符集使用ISO 8859-1 stream.Open stream.WriteText str '打印Stream对象中的数据 Call printStream(stream) 'Stream对象中的数据,已经是我们想要的二进制形式了,可以读取了 stream.Position=0 stream.Charset = targetCharset '按指定编码读取文本 bstr2Text = stream.ReadText stream.Close Set stream = nothing End Function %>
运行结果如图2-27所示。
图2-27 BSTR转换为文本
(6)文本转换为二进制数据
文本转换为二进制数据很简单,写入文本,按二进制方式读取即可。Charset为Unicode或UTF-8时,会自动加入2个或3个字节的前缀。如果不需要它们,则读取时应该跳过,以下的范例是跳过前缀的。
'textData:文本数据 'CharSet:字符集 Function TextToBinary(textData, CharSet) Dim stream Set stream = Server.CreateObject("ADODB.Stream") stream.Type = 2 '文本方式 stream.Charset = CharSet stream.Open stream.WriteText textData '写入文本数据 stream.Position = 0 stream.Type = 1 '二进制方式 If UCase(CharSet) = "UTF-8" Then stream.Position= 3 '跳过3个字节的前缀 ElseIf UCase(CharSet) = "UNICODE" Then stream.Position= 2 '跳过2个字节 End If TextToBinary = stream.Read '读取二进制数据 stream.Close Set stream = nothing End Function
(7)读取文件的二进制数据
在文件下载的系统中,通常需要用到此功能。使用Stream对象读入文件的数据,然后使用Response对象的BinaryWrite方法输出给客户端。
'filePath:文件物理路径 Function LoadFileContent(filePath) Dim stream
Set stream = Server.CreateObject("adodb.stream") stream.Type = 1 '二进制方式 stream.Open stream.LoadFromFile filePath LoadFileContent = stream.Read '读取文件内容 stream.Close Set stream = nothing End Function
(8)二进制数据保存到文件
此功能的用途是很广泛的,如将接收到的Request数据、XMLHttp远程取得的数据或数据库的文件数据等二进制数据保存到文件。
'---------------写入文件------------------------ 'filePath:文件物理路径 'byteData:二进制数据 Sub WriteToFile(filePath, byteData) Dim stream Set stream = Server.CreateObject("adodb.stream") stream.Type = 1 '二进制方式 stream.Open stream.Write byteData stream.SaveToFile filePath,2 '文件存在则覆盖 stream.Close Set stream = nothing End Sub