第十二章 Caché 使用流
流提供了一种存储大量数据(比长字符串限制长)的方法。可以在任何对象类中定义流属性。还可以为其他目的定义独立的流对象,例如用作方法参数或返回值。本章介绍流和流属性.
流类简介
Caché提供以下流类:
- %Stream.GlobalCharacter — 存储在全局节点中的字符流的类。
- %Stream.GlobalBinary — 存储在全局节点中的二进制流的类。
- %Stream.FileCharacter — 存储在外部文件中的字符流的类。
- %Stream.FileBinary — 存储在外部文件中的二进制流的类。
这些类都继承自%Stream.Object,后者定义了公共流接口。
%Library包还包括流类,但已弃用。Caché库包含其他流类,但这些类不适合一般使用。
请注意,流类是对象类。因此,流是一个对象。
声明流属性
Caché同时支持二进制流和字符流.二进制流包含与%Binary类型相同的数据种类,并且适用于非常大的二进制对象,例如图片。同样,字符流包含与%String类型相同的数据,并用于存储大量文本。字符流(如字符串)可能会在客户端应用程序中进行Unicode转换。
流数据可以存储在外部文件或全局Caché中,具体取决于流属性的定义方式:
- %Stream.FileCharacter和%Stream.FileBinary类用于存储为外部文件的流。
- %Stream.GlobalCharacter和%Stream.GlobalBinary类用于存储为全局变量的流。
所有这四个类都可以使用可选的LOCATION参数来指定默认存储位置。
在以下示例中,JournalEntry类包含四个流属性(四个基本流类中的每个流属性),并为其中两个指定默认存储位置:
Class testPkg.JournalEntry Extends %Persistent
{
Property DailyText As %Stream.FileCharacter;
Property DailyImage As %Stream.FileBinary(LOCATION = "E:/Images");
Property Text As %Stream.GlobalCharacter(LOCATION = "^MyText");
Property Picture As %Stream.GlobalBinary;
}
在此示例中,DailyImage的数据存储在E:/ Images目录中的文件中(具有自动生成的名称),而Text属性的数据存储在名为^ MyText的全局文件中。
/// d ##class(PHA.OP.MOB.Test).TestUseStream()
ClassMethod TestUseStream()
{
set stream=##class(User.MyClass).%New()
s stream.PropertyName="S3"
s stream.PropertyName1="S3"
d stream.DailyText.Write("DailyText")
d stream.DailyImage.Write("DailyImage")
d stream.Text.Write("Text")
d stream.Picture.Write("Picture")
s sc=stream.%Save()
w sc,!
}
DHC-APP>zw ^MyText
^MyText=1
^MyText(1)=1
^MyText(1,0)=4
^MyText(1,1)="Text"
使用流接口
所有流都继承了一组用于处理它们包含的数据的方法和属性。下一节列出了常用的方法和属性,以下各节提供了使用它们的具体示例:
常用的流方法和属性
一些常用的方法包括:
- Read() — 从流的当前位置开始读取指定数量的字符。
- Write() — 从当前位置开始,将数据追加到流中。如果位置未设置为流的末尾,则覆盖现有数据。
- Rewind() — 移至流的开头
- MoveTo() — 移动到流中的给定位置。
注意:OpenVMS不支持移动到文件中的某个位置(或提供文件中的当前位置)。
- MoveToEnd() — 移动到流的末尾。
- CopyFrom() — 将源流的内容复制到此流中。
- NewFileName() — 为%Stream.FileCharacter或%Stream.FileBinary属性指定文件名。
常用的属性包括:
- AtEnd — 当读取遇到数据源的末尾时,设置为true:
- Id — 在%Location指定的范围内,流实例的唯一标识符。
- Size — 流的当前大小(以字节或字符为单位,取决于流的类型)。
注意:在OpenVMS上,如果文件包含字节顺序标记(BOM),则为流计算的大小可能包括BOM的大小。
读写流数据
流接口的核心是方法Read(),Write()和Rewind()以及属性AtEnd和Size。
下面的示例从Person.Memo流中读取数据,并将数据一次写入100个字符。len的值通过引用传递,并在每次读取前重置为100。Read方法尝试读取len指定的字符数,然后将其设置为实际读取的字符数:
Do person.Memo.Rewind()
While (person.Memo.AtEnd = 0) {
Set len = 100
Write person.Memo.Read(.len)
}
/// d ##class(PHA.OP.MOB.Test).TestReadStream()
ClassMethod TestReadStream()
{
s a=##class(%Stream.GlobalCharacter).%New()
d a.Write("abcdefhijklmnopqrstuvwxyz123456789")
d a.Rewind()
While (a.AtEnd = 0) {
Set len = 1
Write a.Read(len)
}
}
同样,可以将数据写入流中:
Do person.Memo.Write("This is some text. ")
Do person.Memo.Write("This is some more text.")
指定转换表
如果正在使用除语言环境的本机字符集以外的任何字符集读取或写入类型为%Stream.FileCharacter的流,则必须设置该流的TranslateTable属性。
在流之间复制数据
所有流都包含一个CopyFrom()方法,该方法允许一个流从另一流填充自身。例如,这可用于将数据从文件复制到流属性中。在这种情况下,将使用%Library.File类,该类是操作系统命令的包装,并允许您将文件作为流打开。
在这种情况下,代码为:
/// d ##class(PHA.OP.MOB.Test).TestReadFile()
ClassMethod TestReadFile()
{
Set file = ##class(%File).%New("E:\textfile.txt")
Do file.Open("RU")
s a=##class(%Stream.GlobalCharacter).%New()
Do a.CopyFrom(file)
Write a.Read()
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestReadFile()
this is a file.
还可以使用Set命令将数据复制到流中:
Set person2.Memo = person1.Memo
其中Person类的Memo属性为流保存OREF,此命令将person1.Memo的内容复制到person2.Memo。
注意:以这种方式将Set与两个流一起使用时,不会将一个流的OREF复制到另一个流中-而是专门复制该流的内容。对于流的旧版实现(通过%AbstractStream,%FileBinaryStream,%FileCharacterStream,%GlobalBinaryStream和%GlobalCharacterStream),行为有所不同:在以前的实现中,此上下文中的Set命令复制了OREF。
插入流数据
流同时具有临时和永久存储位置。所有插入内容都进入临时存储区,仅在保存流时才成为永久存储区。如果开始插入流中,然后决定要放弃插入,则不会更改永久位置中存储的数据。
如果创建流,然后开始插入,然后进行一些读取,则可以调用MoveToEnd(),然后继续追加到临时流数据中。但是,保存流后,数据将移动到永久存储位置。如果然后重新加载流并开始插入,它将插入临时存储区域,而不是附加到永久存储的数据。
如果这是想要的行为,则需要创建一个临时流,例如:
Set test = ##class(Test).%OpenId(5)
Set tmpstream = ##class(%Stream.GlobalCharacter).%New()
Do tmpstream.CopyFrom(test.text)
Do tmpstream.MoveToEnd()
Do tmpstream.Write("append text")
Set test.text = tmpstream
Set tmpstream = ""
// Now do whatever you want with the test object
/// d ##class(PHA.OP.MOB.Test).TestInsStream()
ClassMethod TestInsStream()
{
s a=##class(%Stream.GlobalCharacter).%New()
d a.Write("abcdefhijklmnopqrstuvwxyz123456789")
Set tmpstream = ##class(%Stream.GlobalCharacter).%New()
Do tmpstream.CopyFrom(a)
Do tmpstream.MoveToEnd()
Do tmpstream.Write("append text")
Write tmpstream.Read()
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestInsStream()
abcdefhijklmnopqrstuvwxyz123456789append text
在此示例中,我们创建所需类型的临时流,然后从Test对象中存储的流中进行复制,这会将这些数据放入新流的临时存储区中。然后我们附加到此流,并将其OREF放入Test对象,并关闭它以保持引用计数正确。
在流中查找值
流接口包括FindAt()方法,您可以使用该方法查找给定文字值的位置。此方法具有以下参数:
method FindAt(position As %Integer, target, ByRef tmpstr, caseinsensitive As %Boolean = 0) as %Integer
position 是开始搜索的位置。
target 是要搜索的文字值。
tmpstr 通过引用传递的tmpstr返回可以在下一次对FindAt()的调用中使用的信息。当要从找到目标的最后一个位置开始重复搜索相同的流时,请使用此选项。
在这种情况下,将位置指定为–1并在每个调用中通过引用传递tmpstr。然后,每次对FindAt()的后续调用都将从上次调用中断的地方开始。
- caseinsensitive指定是否执行不区分大小写的搜索。默认情况下,该方法不考虑大小写。
该方法从流的开头开始返回此匹配项的位置。如果找不到匹配项,则返回-1。
/// d ##class(PHA.OP.MOB.Test).TestFindAt()
ClassMethod TestFindAt()
{
s a=##class(%Stream.GlobalCharacter).%New()
d a.Write("abcdefhijklmnopqrstuvwxyz123456789")
s b=""
w a.FindAt(1,"C",.b,1),!
w b,!
w a.FindAt(1,"C",.b,0),!
w b,!
}
DHC-APP> d ##class(PHA.OP.MOB.Test).TestFindAt()
3
defhijklmnopqrstuvwxyz123456789
-1
abcdefhijklmnopqrstuvwxyz123456789
在对象应用程序中使用流
流属性是通过拥有该流属性的对象创建的临时对象来操纵的。流充当文字值(将其视为大字符串)。两个对象实例不能引用相同的流。
在以下示例中,创建了一个很长的备忘录,然后将其写入控制台:
// create object and stream
Set p = ##class(Person).%New()
Set p.Name = "Mo"
Do p.Memo.Write("This is part one of a long memo")
// ...
Do p.Memo.Write("This is part 10000 of a long memo")
Do p.%Save()
Set p = ""
// read object and stream
Set p = ##class(Person).%Open(oid)
Do p.Memo.Rewind() // not required first time
// write contents of stream to console, 100 characters at a time
While (p.Memo.AtEnd = 0) {
Set len = 100
Write p.Memo.Read(.len)
}
Set p = ""
与gzip文件一起使用的流类
%Stream包还定义了专用的流类%Stream.FileBinaryGzip和%Stream.FileCharacterGzip,可用于读取和写入gzip文件。它们使用前面所述的相同接口。请注意以下几点:
- 对于这些类,Size属性返回未压缩的大小。当访问Size属性时,Caché会读取数据以计算文件的大小,这可能是一项费时的操作。
- 当访问Size属性,时Caché将倒带该流并将其留在开始时。
流属性到SQL和ODBC的投影
如本书前面所述,一个持久化类被映射为一个SQL表。对于此类,字符流属性和二进制流属性被映射为SQL(和ODBC客户端)作为BLOB(二进制大对象)。
流属性使用ODBC类型LONG VARCHAR(或LONG VARBINARY)进行映射。ODBC驱动程序/服务器使用特殊的协议来读取/写入BLOB。通常,必须手动编写BLOB应用程序,因为标准报告工具不支持它们。
流字段在SQL中具有以下限制:
- 除了一些特定的例外,不能在WHERE子句中使用流值。
- 不能更新/插入包含流的多行;必须逐行进行。
通过嵌入式SQL读取流
可以使用嵌入式SQL读取流,如下所示:
- 使用嵌入式SQL选择流的ID:
&sql(SELECT Memo INTO :memo FROM Person WHERE Person.ID = 12345)
这将获取流的ID,并将其放入memo host 变量中
- 然后打开流并照常处理
通过嵌入式SQL写入流
要通过嵌入式SQL编写流,有几种选择。对于要插入的值,可以使用流的对象引用(OREF),此类OREF的字符串版本或字符串文字。
以下示例显示了所有这些技术。对于这些示例,假设有一个名为Test.ClassWStream的表,该表具有一个名为Prop1的列,该列需要一个流值。
以下示例使用对象引用:
///use an OREF
ClassMethod Insert1()
{
set oref=##class(%Stream.GlobalCharacter).%New()
do oref.Write("Technique 1")
//do the insert; this time use an actual OREF
&sql(INSERT INTO Test.ClassWStreams (Prop1) VALUES (:oref))
}
下一个示例使用对象引用的字符串版本:
///use a string version of an OREF
ClassMethod Insert2()
{
set oref=##class(%Stream.GlobalCharacter).%New()
do oref.Write("Technique 2")
//next line converts OREF to a string OREF
set string=oref_""
//do the insert
&sql(INSERT INTO Test.ClassWStreams (Prop1) VALUES (:string))
}
最后一个示例将字符串文字插入流Prop1中:
///insert a string literal into the stream column
ClassMethod Insert3()
{
set literal="Technique 3"
//do the insert; use a string
&sql(INSERT INTO Test.ClassWStreams (Prop1) VALUES (:literal))
}
注意:字符串文字的第一个字符不能为数字。如果是数字,则SQL会将其解释为OREF并尝试将其归档。由于该流不是OREF,因此将导致SQL -415错误。