流提供了一种存储大量数据(比长字符串限制长)的方法。可以在任何对象类中定义流属性。还可以为其他目的定义独立的流对象,例如用作方法参数或返回值。本章介绍流和流属性.
Caché提供以下流类:
这些类都继承自%Stream.Object,后者定义了公共流接口。
%Library包还包括流类,但已弃用。Caché库包含其他流类,但这些类不适合一般使用。
请注意,流类是对象类。因此,流是一个对象。
Caché同时支持二进制流和字符流.二进制流包含与%Binary类型相同的数据种类,并且适用于非常大的二进制对象,例如图片。同样,字符流包含与%String类型相同的数据,并用于存储大量文本。字符流(如字符串)可能会在客户端应用程序中进行Unicode转换。
流数据可以存储在外部文件或全局Caché中,具体取决于流属性的定义方式:
所有这四个类都可以使用可选的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"
所有流都继承了一组用于处理它们包含的数据的方法和属性。下一节列出了常用的方法和属性,以下各节提供了使用它们的具体示例:
一些常用的方法包括:
注意:OpenVMS不支持移动到文件中的某个位置(或提供文件中的当前位置)。
常用的属性包括:
注意:在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()的后续调用都将从上次调用中断的地方开始。
该方法从流的开头开始返回此匹配项的位置。如果找不到匹配项,则返回-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 = ""
%Stream包还定义了专用的流类%Stream.FileBinaryGzip和%Stream.FileCharacterGzip,可用于读取和写入gzip文件。它们使用前面所述的相同接口。请注意以下几点:
如本书前面所述,一个持久化类被映射为一个SQL表。对于此类,字符流属性和二进制流属性被映射为SQL(和ODBC客户端)作为BLOB(二进制大对象)。
流属性使用ODBC类型LONG VARCHAR(或LONG VARBINARY)进行映射。ODBC驱动程序/服务器使用特殊的协议来读取/写入BLOB。通常,必须手动编写BLOB应用程序,因为标准报告工具不支持它们。
流字段在SQL中具有以下限制:
可以使用嵌入式SQL读取流,如下所示:
&sql(SELECT Memo INTO :memo FROM Person WHERE Person.ID = 12345)
这将获取流的ID,并将其放入memo host 变量中
要通过嵌入式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错误。