深入解析ASP核心技术
上QQ阅读APP看书,第一时间看更新

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