PHP流(Stream)的概述与使用详解

PHP流(Stream)的概述与使用详解,第1张

在现代 PHP 特性中,流或许是最出色但使用率最低的。虽然 PHP 4.3 就引入了流,但是很多开发者并不知道流的存在,因为人们很少提及流,而且流的文档也很匮乏。PHP 官方文档对流的解释如下:

可能看完这段解释后还是云里雾里,我们简化一下,流的作用是在出发地和目的地之间传输数据。出发地和目的地可以是文件、命令行进程、网络连接、ZIP 或 TAR 压缩文件、临时内存、标准输入或输出,或者是通过 PHP 流封装协议实现的任何其他资源。

如果你读写过文件,就用过流;如果你从 php://stdin 读取过数据,或者把输入写入 php://stdout ,也用过流。流为 PHP 的很多 IO 函数提供了底层实现,如 file_get_contents、fopn、fread 和 fwrite 等。PHP 的流函数提供了不同资源的统一接口。

我们可以把流比作管道,把水(资源数据)从一个地方引到另一个地方。在水从出发地到目的地的过程中,我们可以过滤水,可以改变水质,可以添加水,也可以排出水。

流式数据的种类各异,每种类型需要独特的协议,以便读写数据,我们称这些协议为 流封装协议 。例如,我们可以读写文件系统,可以通过 HTTP、HTTPS 或 SSH 与远程 Web 服务器通信,还可以打开并读写 ZIP、RAR 或 PHAR 压缩文件。这些通信方式都包含下述相同的过程:

1.开始通信

2.读取数据

3.写入数据

4.结束通信

虽然过程是一样的,但是读写文件系统中文件的方式与收发 HTTP 消息的方式有所不同,流封装协议的作用是使用通用的接口封装这种差异。

每个流都有一个协议和一个目标。指定协议和目标的方法是使用流标识符:<scheme>://<target>,其中 <scheme>是流的封装协议,<target>是流的数据源。

http://流封装协议

下面使用 HTTP 流封装协议创建了一个与 Flicker API 通信的 PHP 流:

不要以为这是普通的网页 URL,file_get_contents() 函数的字符串参数其实是一个流标识符。http 协议会让 PHP 使用 HTTP 流封装协议,在这个参数中,http 之后是流的目标。

我们通常使用 file_get_contents()、fopen()、fwrite() 和 fclose() 等函数读写文件系统,因为 PHP 默认使用的流封装协议是 file://,所以我们很少认为这些函数使用的是 PHP 流。下面的示例演示了使用 file:// 流封装协议创建一个读写 /etc/hosts 文件的流:

我们通常会省略掉 file:// 协议,因为这是 PHP 使用的默认值。

php://流封装协议

编写命令行脚本的 PHP 开发者会感激 php:// 流封装协议,这个流封装协议的作用是与 PHP 脚本的标准输入、标准输出和标准错误文件描述符通信。我们可以使用 PHP 提供的文件系统函数打开、读取或写入下面四个流:

1. php://stdin :这是个只读 PHP 流,其中的数据来自标准输入。PHP 脚本可以使用这个流接收命令行传入脚本的信息;

2. php://stdout :把数据写入当前的输出缓冲区,这个流只能写,无法读或寻址;

3. php://memory :从系统内存中读取数据,或者把数据写入系统内存。缺点是系统内存有限,所有使用 php://temp 更安全;

4. php://temp :和 php://memory 类似,不过,没有可用内存时,PHP 会把数据写入这个临时文件。

其他流封装协议

PHP 和 PHP 扩展还提供了很多其他流封装协议,例如,与 ZIP 和 TAR 压缩文件、FTP 服务器、数据压缩库、Amazon API、Dropbox API 等通信的流封装协议。需要注意的是,PHP 中的 fopen()、fgets()、fputs()、feof() 以及 fclose() 等函数不仅可以用来处理文件系统中的文件,还可以在所有支持这些函数的流封装协议中使用。

自定义流封装协议

我们还可以自己编写 PHP 流封装协议。PHP 提供了一个示例 StreamWrapper 类,演示如何编写自定义的流封装协议,支持部分或全部 PHP 文件系统函数。关于如何编写,具体请参考以下文档:

http://php.net/manual/zh/class.streamwrapper.php

http://php.net/manual/zh/stream.streamwrapper.example-1.php

有些 PHP 流能够接受一系列可选的参数,这些参数叫流上下文,用于定制流的行为。不同的流封装协议使用的流上下文有所不同,流上下文使用 stream_context_create() 函数创建,这个函数返回的上下文对象可以传入大多数文件系统函数。

例如,你知道可以使用 file_get_contents() 发送 HTTP POST 请求吗?使用一个流上下文对象即可实现:

流过滤器

目前为止我们讨论了如何打开流,读取流中的数据,以及把数据写入流。不过,PHP 流真正强大的地方在于过滤、转换、添加或删除流中传输的数据,例如,我们可以打开一个流处理 Markdown 文件,在把文件内容读入内存的过程中自动将其转化为 HTML。

运行该脚本,输出的都是大写字母:

我们还可以使用 php://filter 流封装协议把过滤器附加到流上,不过,使用这种方式之前必须先打开 PHP 流:

这个方式实现效果和 stream_filter_append() 函数一样,但是相比之下更为繁琐。不过,PHP 的某些文件系统函数在调用后无法附加过滤器,例如 file() 和 fpassthru(),使用这些函数时只能使用 php://filter 流封装协议附加流过滤器。

自定义流过滤器

我们还可以编写自定义的流过滤器。其实,大多数情况下都要使用自定义的流过滤器,自定义的流过滤器是个 PHP 类,继承内置的 php_user_filter 类( http://php.net/manual/zh/class.php-user-filter.php ),且必须实现 filter()、onCreate() 和 onClose() 方法,最后,必须使用 stream_filter_register() 函数注册自定义的流过滤器。

然后,我们必须使用 stream_filter_register() 函数注册这个自定义的 DirtyWordsFilter 流过滤器:

第一个参数用于标识这个自定义过滤器的过滤器名,第二个参数是这个自定义过滤器的类名。接下来就可以使用这个自定义的流过滤器了:

修改 test.txt 内容如下:

运行上面的自定义过滤器脚本,结果如下:

stream_bucket_append函数:为队列添加数据 

stream_bucket_make_writeable函数:从 *** 作的队列中返回一个数据对象

stream_bucket_new函数:为当前队列创建一个新的数据

stream_bucket_prepend函数:预备数据到队列 

stream_context_create函数:创建数据流上下文

stream_context_get_default函数:获取默认的数据流上下文

stream_context_get_options函数:获取数据流的设置

stream_context_set_option函数:对数据流、数据包或者上下文进行设置

stream_context_set_params函数:为数据流、数据包或者上下文设置参数

stream_copy_to_stream函数:在数据流之间进行复制 *** 作

stream_filter_append函数:为数据流添加过滤器

stream_filter_prepend函数:为数据流预备添加过滤器

stream_filter_register函数:注册一个数据流的过滤器并作为PHP类执行

stream_filter_remove函数:从一个数据流中移除过滤器

stream_get_contents函数:读取数据流中的剩余数据到字符串

stream_get_filters函数:返回已经注册的数据流过滤器列表

stream_get_line函数:按照给定的定界符从数据流资源中获取行

stream_get_meta_data函数:从封装协议文件指针中获取报头/元数据

stream_get_transports函数:返回注册的Socket传输列表

stream_get_wrappers函数:返回注册的数据流列表

stream_register_wrapper函数:注册一个用PHP类实现的URL封装协议

stream_select函数:接收数据流数组并等待它们状态的改变

stream_set_blocking函数:将一个数据流设置为堵塞或者非堵塞状态

stream_set_timeout函数:对数据流进行超时设置

stream_set_write_buffer函数:为数据流设置缓冲区

stream_socket_accept函数:接受由函数stream_ socket_server()创建的Socket连接

stream_socket_client函数:打开网络或者UNIX主机的Socket连接

stream_socket_enable_crypto函数:为一个已经连接的Socket打开或者关闭数据加密

stream_socket_get_name函数:获取本地或者网络Socket的名称

stream_socket_pair函数:创建两个无区别的Socket数据流连接

stream_socket_recvfrom函数:从Socket获取数据,不管其连接与否

stream_socket_sendto函数:向Socket发送数据,不管其连接与否

stream_socket_server函数:创建一个网络或者UNIX Socket服务端

stream_wrapper_restore函数:恢复一个事先注销的数据包

stream_wrapper_unregister函数:注销一个URL地址包

整合资料

本文整合于以下两篇文章

https://blog.csdn.net/qq756684177/article/details/81518647

https://xueyuanjun.com/post/7459.html

通过使用一些辅助性工具来找到程序中的瓶颈,然后就可以对瓶颈部分的代码进行优化。一般有两种方案:即优化代码或更改设计方法。我们一般会选择后者,因为不去调用以下代码要比调用一些优化的代码更能提高程序的性能。而一个设计良好的程序能够精简代码,从而提高性能。

下面将提供一些在JAVA程序的设计和编码中,为了能够提高JAVA程序的性能,而经常采用的一些方法和技巧。

1.对象的生成和大小的调整。

JAVA程序设计中一个普遍的问题就是没有好好的利用JAVA语言本身提供的函数,从而常常会生成大量的对象(或实例)。由于系统不仅要花时间生成对象,以后可能还需花时间对这些对象进行垃圾回收和处理。因此,生成过多的对象将会给程序的性能带来很大的影响。

例1:关于String ,StringBuffer,+和append

JAVA语言提供了对于String类型变量的 *** 作。但如果使用不当,会给程序的性能带来影响。如下面的语句:

String name=new String("HuangWeiFeng")

System.out.println(name+"is my name")

看似已经很精简了,其实并非如此。为了生成二进制的代码,要进行如下的步骤和 *** 作:

(1) 生成新的字符串 new String(STR_1)

(2) 复制该字符串

(3) 加载字符串常量"HuangWeiFeng"(STR_2)

(4) 调用字符串的构架器(Constructor)

(5) 保存该字符串到数组中(从位置0开始)

(6) 从java.io.PrintStream类中得到静态的out变量

(7) 生成新的字符串缓冲变量new StringBuffer(STR_BUF_1)

(8) 复制该字符串缓冲变量

(9) 调用字符串缓冲的构架器(Constructor)

(10) 保存该字符串缓冲到数组中(从位置1开始)

(11) 以STR_1为参数,调用字符串缓冲(StringBuffer)类中的append方法

(12) 加载字符串常量"is my name"(STR_3)

(13) 以STR_3为参数,调用字符串缓冲(StringBuffer)类中的append方法

(14) 对于STR_BUF_1执行toString命令

(15) 调用out变量中的println方法,输出结果。

由此可以看出,这两行简单的代码,就生成了STR_1,STR_2,STR_3,STR_4和STR_BUF_1五个对象变量。这些生成的类的实例一般都存放在堆中。堆要对所有类的超类,类的实例进行初始化,同时还要调用类极其每个超类的构架器。而这些 *** 作都是非常消耗系统资源的。因此,对对象的生成进行限制,是完全有必要的。

经修改,上面的代码可以用如下的代码来替换。

StringBuffer name=new StringBuffer("HuangWeiFeng")

System.out.println(name.append("is my name.").toString())

系统将进行如下的 *** 作:

(1) 生成新的字符串缓冲变量new StringBuffer(STR_BUF_1)

(2) 复制该字符串缓冲变量

(3) 加载字符串常量"HuangWeiFeng"(STR_1)

(4) 调用字符串缓冲的构架器(Constructor)

(5) 保存该字符串缓冲到数组中(从位置1开始)

(6) 从java.io.PrintStream类中得到静态的out变量

(7) 加载STR_BUF_1

(8) 加载字符串常量"is my name"(STR_2)

(9) 以STR_2为参数,调用字符串缓冲(StringBuffer)实例中的append方法

(10) 对于STR_BUF_1执行toString命令(STR_3)

(11)调用out变量中的println方法,输出结果。

由此可以看出,经过改进后的代码只生成了四个对象变量:STR_1,STR_2,STR_3和STR_BUF_1.你可能觉得少生成一个对象不会对程序的性能有很大的提高。但下面的代码段2的执行速度将是代码段1的2倍。因为代码段1生成了八个对象,而代码段2只生成了四个对象。

代码段1:

String name= new StringBuffer("HuangWeiFeng")

name+="is my"

name+="name"

代码段2:

StringBuffer name=new StringBuffer("HuangWeiFeng")

name.append("is my")

name.append("name.").toString()

因此,充分的利用JAVA提供的库函数来优化程序,对提高JAVA程序的性能时非常重要的.其注意点主要有如下几方面;

(1) 尽可能的使用静态变量(Static Class Variables)

如果类中的变量不会随他的实例而变化,就可以定义为静态变量,从而使他所有的实例都共享这个变量。

例:

public class foo

{

 SomeObject so=new SomeObject()

}

就可以定义为:

public class foo

{

 static SomeObject so=new SomeObject()

}

(2) 不要对已生成的对象作过多的改变。

对于一些类(如:String类)来讲,宁愿在重新生成一个新的对象实例,而不应该修改已经生成的对象实例。

例:

String name="Huang"

name="Wei"

name="Feng"

上述代码生成了三个String类型的对象实例。而前两个马上就需要系统进行垃圾回收处理。如果要对字符串进行连接的 *** 作,性能将得更差,因为系统将不得为此生成更多得临时变量,如上例1所示。

(3) 生成对象时,要分配给它合理的空间和大小JAVA中的很多类都有它的默认的空间分配大小。对于StringBuffer类来讲,默认的分配空间大小是16个字符。如果在程序中使用StringBuffer的空间大小不是16个字符,那么就必须进行正确的初始化。

(4) 避免生成不太使用或生命周期短的对象或变量。对于这种情况,因该定义一个对象缓冲池。以为管理一个对象缓冲池的开销要比频繁的生成和回收对象的开销小的多。

(5) 只在对象作用范围内进行初始化。JAVA允许在代码的任何地方定义和初始化对象。这样,就可以只在对象作用的范围内进行初始化。从而节约系统的开销。

例:

SomeObject so=new SomeObject()

If(x==1) then

{

 Foo=so.getXX()

}

可以修改为:

if(x==1) then

{

 SomeObject so=new SomeObject()

 Foo=so.getXX()

}

2.异常(Exceptions)

JAVA语言中提供了try/catch来发方便用户捕捉异常,进行异常的处理。但是如果使用不当,也会给JAVA程序的性能带来影响。因此,要注意以下两点:

(1) 避免对应用程序的逻辑使用try/catch

如果可以用if,while等逻辑语句来处理,那么就尽可能的不用try/catch语句。

(2) 重用异常

在必须要进行异常的处理时,要尽可能的重用已经存在的异常对象。以为在异常的处理中,生成一个异常对象要消耗掉大部分的时间。

3. 线程(Threading)

一个高性能的应用程序中一般都会用到线程。因为线程能充分利用系统的资源。在其他线程因为等待硬盘或网络读写而 时,程序能继续处理和运行。但是对线程运用不当,也会影响程序的性能。

例2:正确使用Vector类

Vector主要用来保存各种类型的对象(包括相同类型和不同类型的对象)。但是在一些情况下使用会给程序带来性能上的影响。这主要是由Vector类的两个特点所决定的。第一,Vector提供了线程的安全保护功能。即使Vector类中的许多方法同步。但是如果你已经确认你的应用程序是单线程,这些方法的同步就完全不必要了。第二,在Vector查找存储的各种对象时,常常要花很多的时间进行类型的匹配。而当这些对象都是同一类型时,这些匹配就完全不必要了。因此,有必要设计一个单线程的,保存特定类型对象的类或集合来替代Vector类.用来替换的程序如下(StringVector.java):

public class StringVector

{

 private String [] data

 private int count

 public StringVector()

 {

this(10)// default size is 10

 }

 public StringVector(int initialSize)

 {

data = new String[initialSize]

 }

 public void add(String str)

 {

 // ignore null strings

 if(str == null) { return}

 ensureCapacity(count + 1)

 data[count++] = str

 }

 private void ensureCapacity(int minCapacity)

 {

int oldCapacity = data.length

if (minCapacity >oldCapacity)

{

 String oldData[] = data

 int newCapacity = oldCapacity * 2

 data = new String[newCapacity]

 System.arraycopy(oldData, 0, data, 0, count)

}

 }

 public void remove(String str)

 {

 if(str == null) { return// ignore null str }

 for(int i = 0i <counti++)

 {

// check for a match

if(data[i].equals(str))

{

 System.arraycopy(data,i+1,data,i,count-1)// copy data

 // allow previously valid array element be gc'd

 data[--count] = null

 return

}

 }

 }

 public final String getStringAt(int index)

 {

 if(index <0) { return null}

 else if(index >count) { return null// index is ># strings }

 else { return data[index]// index is good }

 }

}

因此,代码:

Vector Strings=new Vector()

Strings.add("One")

Strings.add("Two")

String Second=(String)Strings.elementAt(1)

可以用如下的代码替换:

StringVector Strings=new StringVector()

Strings.add("One")

Strings.add("Two")

String Second=Strings.getStringAt(1)

这样就可以通过优化线程来提高JAVA程序的性能。用于测试的程序如下(TestCollection.java):

import java.util.Vector

public class TestCollection

{

 public static void main(String args [])

 {

TestCollection collect = new TestCollection()

if(args.length == 0)

{

 System.out.println("Usage: java TestCollection [ vector | stringvector ]")

 System.exit(1)

}

if(args[0].equals("vector"))

{

 Vector store = new Vector()

 long start = System.currentTimeMillis()

 for(int i = 0i <1000000i++)

 {

store.addElement("string")

 }

 long finish = System.currentTimeMillis()

 System.out.println((finish-start))

 start = System.currentTimeMillis()

 for(int i = 0i <1000000i++)

 {

String result = (String)store.elementAt(i)

 }

 finish = System.currentTimeMillis()

 System.out.println((finish-start))

}

else if(args[0].equals("stringvector"))

{

 StringVector store = new StringVector()

 long start = System.currentTimeMillis()

 for(int i = 0i <1000000i++) { store.add("string")}

 long finish = System.currentTimeMillis()

 System.out.println((finish-start))

 start = System.currentTimeMillis()

 for(int i = 0i <1000000i++) {

String result = store.getStringAt(i)

 }

 finish = System.currentTimeMillis()

 System.out.println((finish-start))

}

 }

}

关于线程的 *** 作,要注意如下几个方面:

(1) 防止过多的同步

如上所示,不必要的同步常常会造成程序性能的下降。因此,如果程序是单线程,则一定不要使用同步。

(2) 同步方法而不要同步整个代码段

对某个方法或函数进行同步比对整个代码段进行同步的性能要好。

(3) 对每个对象使用多”锁”的机制来增大并发。

一般每个对象都只有一个”锁”,这就表明如果两个线程执行一个对象的两个不同的同步方法时,会发生”死锁”。即使这两个方法并不共享任何资源。为了避免这个问题,可以对一个对象实行”多锁”的机制。如下所示:

class foo

{

 private static int var1

 private static Object lock1=new Object()

 private static int var2

 private static Object lock2=new Object()

 public static void increment1()

 {

synchronized(lock1)

{

 var1++

}

 }

 public static void increment2()

 {

synchronized(lock2)

{

 var2++

}

 }

}

4.输入和输出(I/O)

输入和输出包括很多方面,但涉及最多的是对硬盘,网络或数据库的读写 *** 作。对于读写 *** 作,又分为有缓存和没有缓存的;对于数据库的 *** 作,又可以有多种类型的JDBC驱动器可以选择。但无论怎样,都会给程序的性能带来影响。因此,需要注意如下几点:

(1) 使用输入输出缓冲

尽可能的多使用缓存。但如果要经常对缓存进行刷新(flush),则建议不要使用缓存。

(2) 输出流(Output Stream)和Unicode字符串

当时用Output Stream和Unicode字符串时,Write类的开销比较大。因为它要实现Unicode到字节(byte)的转换.因此,如果可能的话,在使用Write类之前就实现转换或用OutputStream类代替Writer类来使用。

(3) 当需序列化时使用transient

当序列化一个类或对象时,对于那些原子类型(atomic)或可以重建的原素要表识为transient类型。这样就不用每一次都进行序列化。如果这些序列化的对象要在网络上传输,这一小小的改变对性能会有很大的提高。

(4) 使用高速缓存(Cache)

对于那些经常要使用而又不大变化的对象或数据,可以把它存储在高速缓存中。这样就可以提高访问的速度。这一点对于从数据库中返回的结果集尤其重要。

(5) 使用速度快的JDBC驱动器(Driver)

JAVA对访问数据库提供了四种方法。这其中有两种是JDBC驱动器。一种是用JAVA外包的本地驱动器;另一种是完全的JAVA驱动器。具体要使用哪一种得根据JAVA布署的环境和应用程序本身来定。

5.一些其他的经验和技巧

(1) 使用局部变量。

(2) 避免在同一个类中动过调用函数或方法(get或set)来设置或调用变量。

(3) 避免在循环中生成同一个变量或调用同一个函数(参数变量也一样)。

(4) 尽可能的使用static,final,private等关键字。

(5) 当复制大量数据时,使用System.arraycopy()命令。

数据流(data stream)是一组有序,有起点和终点的位元组的数据序列。包括输入流和输出流。

数据流最初是通信领域使用的概念,代表传输中所使用的信息的数字编码信号序列。这个概念最初在1998年由Henzinger在文献87中提出,他将数据流定义为“只能以事先规定好的顺序被读取一次的数据的一个序列”。

基本介绍中文名 :数据流 外文名 :data stream 概念提出人 :Henzinger 提出时间 :1998年 释义 :以规定顺序被读取一次的数据序列 发展原因 :2个 数据模式 :4个 计算类型 :可分为两类:基本计算和复杂计算 产生背景,细节数据,复杂分析,区别特征,分类,输入流与输出流,缓冲流,模型描述,形式化,数据集合,数据属性,计算类型,相关思路,简介,随机采样,构造略图,直方图,小波变换,新动向,小说流派, 产生背景 数据流套用的产生的发展是以下两个因素的结果: 细节数据 已经能够持续自动产生大量的细节数据。这类数据最早出现于传统的银行和股票交易领域,后来则也出现为地质测量、气象、天文观测等方面。尤其是网际网路(网路流量监控,点击流)和无线通信网(通话记录)的出现,产生了大量的数据流类型的数据。我们注意到这类数据大都与地理信息有一定关联,这主要是因为地理信息的维度较大,容易产生这类大量的细节数据。 复杂分析 需要以近实时的方式对更新流进行复杂分析。对以上领域的数据进行复杂分析(如趋势分析,预测)以前往往是(在数据仓库中)脱机进行的,然而一些新的套用(尤其是在网路安全和国家安全领域)对时间都非常敏感,如检测网际网路上的极端事件、欺诈、入侵、异常,复杂人群监控,趋势监控(track trend),探查性分析(exploratory *** yses),和谐度分析(harmonic *** ysis)等,都需要进行在线上的分析。 在此之后,学术界基本认可了这个定义,有的文章也在此基础上对定义稍微进行了修改。例如,S. Guha等[88]认为,数据流是“只能被读取一次或少数几次的点的有序序列”,这里放宽了前述定义中的“一遍”限制。 为什么在数据流的处理中,强调对数据读取次数的限制呢?S. Muthukrishnan[89]指出数据流是指“以非常高的速度到来的输入数据”,因此对数据流数据的传输、计算和存储都将变得很困难。在这种情况下,只有在数据最初到达时有机会对其进行一次处理,其他时候很难再存取到这些数据(因为没有也无法保存这些数据)。 区别特征 与传统的关系数据模式区别 B.Babcock等[90]认为数据流模式在以下几个方面不同于传统的关系数据模式: 1. 数据在线上到达; 2. 处理系统无法控制所处理的数据的到达顺序; 3. 数据可能是无限多的; 4. 由于数据量的庞大,数据流中的元素被处理后将被抛弃或存档(archive)。以后再想获取这些数据将会很困难,除非将数据存储在记忆体中,但由于记忆体大小通常远远小于数据流数据的数量,因此实际上通常只能在数据第一次到达时获取数据。 三个 特点 我们认为,当前所研究的数据流计算之所以不同于传统的计算模式,关键在于这些数据流数据本身具有如下三个特点: 数据的到达—快速 这意味着短时间内可能会有大量的输入数据需要处理。这对处理器和输入输出设备来说都是一个较大的负担,因此对数据流的处理应尽可能简单。 酷睿2处理器 数据的范围—广域 这是指数据属性(维)的取值范围非常大,可能取的值非常多,如地域、手机号码、人、网路节点等。这才是导致数据流无法在记忆体或硬碟中存储的主要原因。如果维度小,即使到来的数据量很大,也可以在较小的存储器中保存这些数据。例如,对于无线通信网来说,同样的100万条通话记录,如果只有1000个用户,那么使用1000个存储单位就可以保存足够多和足够精确的数据来回答“某一用户的累计通话时间有多长”的问题;而如果共有100000个用户,要保存这些信息,就需要100000个存储单位。数据流数据的属性大多与地理信息、IP位址、手机号码等有关,而且往往与时间联系在一起。这时,数据的维度远远超过了记忆体和硬碟容量,这意味着系统无法完整保存这些信息,通常只能在数据到达的时候存取数据一次。 数据到达的时间—持续 数据的持续到达意味着数据量可能是无限的。而且,对数据进行处理的结果不会是最终的结果,因为数据还会不断地到达。因此,对数据流的查询的结果往往不是一次性而是持续的,即随着底层数据的到达而不断返回最新的结果。 以上数据流的特点决定了数据流处理的特点一次存取,持续处理,有限存储, 近似结果,快速回响。 近似结果是在前三个条件限制下产生的必然结果。由于只能存取数据一次,而且只有相对较小的有限空间存储数据,因此产生精确的计算结果通常是不可能的。而将对结果的要求从过去的“精确”改为“近似”后,实现数据流查询的快速回响也就成为了可能。 分类 数据的性质、格式不同,则对流的处理方法也不同,因此,在Java的输入/输出类库中,有不同的流类来对应不同性质的输入/输出流。在java.io包中,基本输入/输出流类可按其读写数据的类型之不同分为两种:位元组流和字元流。 输入流与输出流 数据流分为输入流(InputStream)和输出流(OutputStream)两类。输入流只能读不能写,而输出流只能写不能读。通常程式中使用输入流读出数据,输出流写入数据,就好像数据流入到程式并从程式中流出。采用数据流使程式的输入输出 *** 作独立与相关设备。 输入流可从键盘或档案中获得数据,输出流可向显示器、印表机或档案中传输数据。 缓冲流 为了提高数据的传输效率,通常使用缓冲流(Buffered Stream),即为一个流配有一个缓冲区(buffer),一个缓冲区就是专门用于传输数据的记忆体块。当向一个缓冲流写入数据时,系统不直接传送到外部设备,而是将数据传送到缓冲区。缓冲区自动记录数据,当缓冲区满时,系统将数据全部传送到相应的设备。 当从一个缓冲流中读取数据时,系统实际是从缓冲区中读取数据。当缓冲区空时,系统就会从相关设备自动读取数据,并读取尽可能多的数据充满缓冲区。 模型描述 我们试图从数据集合、数据属性和计算类型三个不同方面对数据流的模型进行归纳和描述。实际上,很多文章提出了各种各样的数据流模型,我们并没有包括所有这些模型,只是将其中比较重要的和常见的进行了归纳和分类。 形式化 以下是对数据流的一个形式化描述。 考虑向量α,其属性的域为[1..n](秩为n),而且向量α在时间t的状态 α(t)=<α1(t), ...αi(t), ...αn(t) >在时刻s,α是0向量,即对于所有i,αi(s)=0。对向量的各个分量的更新是以二元组流的形式出现的。即,第t个更新为(i, ct),意味着αi(t)= αi(t . 1) + ct,且对于i. =.i,αi. (t)= αi. (t . 1)。在时刻t发生的查询是针对α(t)的。 数据集合 我们首先考虑在进行数据流计算时,有哪些数据被包含在计算范围之内。关于这个问题,主要有三种不同的模型:分别是数据流模型(data stream model)、滑动视窗模型(sliding window model)和n-of-N模型。 数据流模型(data stream model)在数据流模型中,从某个特定时间开始的所有数据都要被纳入计算范围。此时,s=0,即在时刻0,α是0向量。即这是数据流最初和最普遍的模型。 滑动视窗模型(sliding window model ,计算最近的N个数据)滑动视窗模型是指,从计算时算起,向前追溯的N个数据要被纳入计算范围。此时,s = t . N,即在时刻t . N,α是0向量。换句话说,要计算最近的N个数据。由于数据流的数据是不断涌现的,所以直观的看,这种模式就像用一个不变的视窗,数据随时间的推移经过视窗,出现视窗内的数据就是被计算的数据集合。M. Datar等[91]首先提出这一模式,随后得到了广泛回响[92]。 n-of-N模型(计算最近的n个数据,其中0 <n ≤ N) 文献[93] 提出的这种模型建立在滑动视窗模型的基础之上,比滑动视窗模型更为灵活:被纳入计算范围的是从计算时算起,向前追溯的n个数据。此时,s = t . n,即在时刻t . n,α是0向量。注意,其中n ≤ N,而且是可以随查询要求变化的。而在滑动视窗模型中,n = N而且是固定不变的。对于数据流处理系统来说,要能够回答所有长度小于等于N的滑动视窗问题。 数据属性 数据本身的特征: 时间序列(time series model) 数据按照其属性(实际上就是时间)的顺序前来。在这种情况下,i = t,即一个t时刻的更新为(t, ct)。此时对α的更新 *** 作为αt(t)= ct, 且对于i. =.t,αi. (t)= αi. (t . 1)。这种模型适用于时序数据,如某特定IP的传出的数据,或股票的定期更新数据等。 收款机模型(cash register model) 同一属性的数据相加,数据为正。在这种模型中,ct >=0。这意味着对于所有的i和t来说,αi(t)总是不小于零,而且是递增的。实际上,这种模型被认为是最常用的,例如可以用于对收款机(收款机模型由此得名),各个IP的网路传输量,手机用户的通话时长的监控等等。 十字转门模型(turnstile model) 同一属性的数据相加,数据为正或负。在这种模型中,ct可以大于0也可以小于0。这是最通用的模型。S. Muthukrishnan[89]称其为十字转门模型起因于这种模型的功能就象捷运站的十字转门,可以用来计算有多少人到达和离开,从而得出捷运中的人数。 计算类型 对数据流数据的计算可以分为两类:基本计算和复杂计算。基本计算主要包括对点查询、范围查询和内积查询这三种查询的计算。复杂计算包括对分位数的计算、频繁项的计算以及数据挖掘等。 点查询(Point query) 返回αi(t)的值。 范围查询(Range query) 对于范围查询Q(f, t),返回 t . αi(t) i=f 内积(Inner product) 对于向量β,α与β的内积 α . β =Σni=1αi(t)βi 分位数(Quantile) 给定一个序号r,返回值v,并确保v在α中的真实排序r.符合以下要求: r . εN ≤ r. ≤ r + εN 其中,ε是精度,N =Σni=1αi(t)。 G. S. Manku等[94]提供了对分位数进行一遍扫描进行近似估计的框架结构,将数据集合看成树的节点,这些节点拥有不同的权重(如节点中包含的数据个数)。认为所有的分位数的估计算法都可以被认为由三个对节点的 *** 作组成产生新节点(NEW) 、合并(COLLAPSE)和输出(OUTPUT)。不同的策略构成了不同类型的树。这个框架结构成为后来很多分位数估计算法的基础。 频繁项(Frequent items)有时也称Heavy hitters,即找出在数据流中频繁出现的项。在这种计算中,实际上令ct =1。这样,αi(t)中保存了截至t时刻,维值等于i的数据到达的频率。对这些数据的查询又可分为两种: 找出头k个最频繁出现的项 找出所有出现频率大于1/k的项 对频率项的研究主要集中在后一种计算[95]。 挖掘对数据流数据进行挖掘涉及更复杂的计算。对这方面的研究包括:多维分析[96],分类分析[97, 98],聚类分析[99–102],以及其他one-pass算法[103]。 相关思路 简介 数据流处理过程中的主要难点在于如何将存储数据所花费的空间控制在一定范围之内。查询回响时间问题虽然也很重要,但相对容易解决。作为研究领域的一个热点,数据流处理问题得到了广泛的研究,出现了很多算法。 解决数据流庞大的数据量与有限的存储空间之间的矛盾的一个思路是使用采样,另一个思路是,构造一个小的、能提供近似结果的数据结构存放压缩的数据流数据,这个结构能存放在存储器中。略图(Sketch)、直方图(histogram)和小波(wavelet)实际上就都是这样的数据结构中最重要的三种。 以上方法实际上大都已用于传统资料库领域,问题在于如何将它们套用于数据流的特殊环境。 随机采样 随机采样(Random sampling)可以通过抽取少量样本来捕捉数据集合的基本特性。一个很常见的简单方法就是一致性采样(uniform sample)。作为一个备选的采样方法分层采样(strati.ed sampling)可以减少数据的不均匀分布所带来的误差。不过,对于复杂的分析,普通的采样算法还是需要太大的空间。 对于数据流的一些特殊计算,已经出现了一些有趣的采样算法。粘采样(Sticky sampling)[95]用于频繁项(frequent items)的计算。粘采样使用的方法是,在记忆体中存放二元组(i,f)所构成的集合S,对于每到来的一个数据,如果其键i已经存在于S,则对应的f加1;否则,以1 r 的机率进行采样,如果该项被选中,在S中增加一组(i,1);每过一段时间,对S中的组进行一遍扫描,对其中的值进行更新。然后增加r的值;结束(或用户要求结果)时,输出所有f.(s-e)N的组。 P. Gibbons提出的distinct sampling[104]用于distinct counting ,即找出数据流中不同值的个数。它使用哈希(hash )函式对每一个到来的不同值以2.(i+1)的机率映射到级别i上;如果i ≥记忆体级别L(L的初始值为0),将其加入记忆体,否则抛弃;记忆体满时,将记忆体中级别为L的值删除,并将L加1;最终对distinct count的估计为记忆体中不同的值乘以2L。distinct counting是资料库处理中的一个老问题,这种算法的优点是,通过设定合适的参数,可套用于带谓词的查询(即对数据流的一个子集进行distinct counting)。 采样算法的缺点是:它们对异常数据不够敏感。而且,即使它们可以很好的套用于普通的数据流模型,但如果要用于滑动视窗模型(sliding window model)[91] 或n-of-N模型[93],还需要进行较大的修改。 构造略图 构造略图(sketching)是指使用随机映射(Random projections)将数据流投射在一个小的存储空间内作为整个数据流的概要,这个小空间存储的概要数据称为略图,可用于近似回答特定的查询。不同的略图可用于对数据流的不同Lp范数的估算,进而这些Lp范数可用于回答其它类型的查询。如L0范数可用于估算数据流的不同值(distinct count);L1范数可用于计算分位数(quantile)和频繁项(frequent items);L2范数可用于估算自连线的长度等等。 略图的概念最早由N. Alon在[105]中提出,从此不断涌现出各种略图及其构造算法。 N. Alon 在[105]中提出的随机略图构造(randomized steching)可以用于对不同Lp范数的估算,最多需要O(n 1. lg n)的空间。该文更重要的贡献在于,它还可以以O(log n + log t)的空间需求估算L2。它的主要思路是,使用哈希函式,将数据属性的域D中的每一个元素一致地随机映射到zi ∈ {.1+ 1}上,令随机变数X = .i αizi,X2就可作为对L2范数的估计。 p1 S. Guha 等[88]提出的分位数略图(quantile sketch) 保持一组形如(vi,gi, Δi)的数据结构,rmax(vi) 和rmin(vi)分别是vi可能的排位的最大和最小值。对于i>j 满足: vi >vj gi = rmin(vi) . rmin(vi . 1) Δi = rmax(vi) . rmin(vi) 随着数据的到来,对此略图进行相应的更新 *** 作,使估算保持在一定的精度之内。X. Lin等[93]对于这个问题做出了更形式化的描述。 若令AS为一个从[1..n]中提取的随机集合,每一个元素被提取的机率为1/2。A. Gilbert 等[106]构造若干个AS,将每个集合中元素值的和称为随机和(random sum)。多个随机和构成一个略图。对αi的估算为 2E(||AS|| |αi ∈ AS) . ||A||, 其中||A||为数据流中所有数的和。因此,这种略图可用于估算点查询的结果。使用多个这样的略图,可用于估算范围查询、分位数查询等。略图技术实际上是空间和精度相权衡的结果。为保证点查询结果的误差小于εN, 上述略图需要的空间通常是以ε.2作为系数的。与此相比较,G. Cormode 等提出的计数-最小略图(Count-Min Sketch )[19]只需要ε.1系数的空间。其思路也比较简单,使用若干个哈希函式将分别数据流投射到多个小的略图上,回答点查询时,每个略图分别作答,并选择值最小的作为答案。以点查询为基础,计数-最小略图可以用于其它各种查询和复杂计算。计数-最小略图并不计算Lp范数,而是直接计算出点查询的结果,这是它的时空效率比其它略图高的原因之一。 直方图 直方图(histogram)有两个含义:一个是普通意义上的直方图,是一种用于显示近似统计的视觉手段;另外,它还是一种捕捉数据的近似分布的数据结构/方法。作为后者出现时时,直方图是这样构造的:将数据按其属性分到多个不相交的子集(称为桶)并用某种统一的方式近似表示桶中的值[107]。 直方图 直方图方法主要用于信号处理、统计、图像处理、计算机视觉和资料库。在资料库领域,直方图原先主要用于选择性估计(selectivity estimation),用于选择查询最佳化和近似查询处理。直方图是一种最简单、最灵活的近似处理方法,同时也是最有效的一种。只要解决好数据更新问题,就可以将原有的直方图运用到数据流处理中。这类根据新的数据自动调节的直方图被称为动态(或自适应/自调节)直方图。 L. Fu等[108]提出的直方图主要用于中值函式(Median )和其他分位数函式的计算,可用于近似计算,也可用于精确查询。它通过确定性分桶(Deterministic Bucketing )和随机分桶(Randomized Bucketing )技术,构造多个不同精度的桶(buckets),然后将输入数据逐级分到这些桶中,从而完成了动态直方图的构造。 由于将静态直方图直接套用到数据流处理比较困难。S. Guha等[88]虽然可以动态地构造近最优的V-optimal 直方图,但只能套用于时间序列模型(time series model) 下的数据流。 一个常采用的方法是将整个算法分为两步:首先构造一个数据流数据的略图;然后从这个略图中构造合适的直方图。这种方法可以利用略图数据易于更新的特点,又能实现直方图的动态化。N. Thaper等[109]首先是构造一个近似反映数据流数据的略图,利用略图的优良的更新性能来实现数据的更新,然后从这个略图中导出一个直方图来实现对数据流数据的近似。由于从略图中导出最佳的直方图是一个NP-hard问题,作者提供了一个启发式算法(贪婪算法)来搜寻一个较佳的直方图。 A. Gilbert等[110]构造了一个概要的数据结构,该结构使用一组与文献[106]中类似的随机和结构来保存不同粒度级别的dyadic interval的值。随后,将不同粒度级别的dyadic interval([111])从大到小地加入所要构造的直方图中,这样就将近似误差降到最低(求精)。 A. Gilbert等在文献[112]中主要考虑的是如何降低对数据流中每个输入数据的处理复杂度。他们先将输入数据转化为小波系数(利用小波系数是信号与基向量的内积),然后采用了与文献[110]类似的dyadic interval处理方法。略图与直方图有很密切的联系,从某种方面来说,可以认为直方图是略图的一种特殊情况。 小波变换 小波变换(wavelet transformation)常用于生成数据的概要信息。这是因为通常小波系数只有很少一部分是重要的,大部分系数或者值很小,或者本身不重要。所以,如果忽略数据经过小波变换后生成的不重要系数,就可以使用很少的空间完成对原数据的近似。 Y. Matias等首先针对数据流数据构造一个直方图,使用小波对其进行模拟。随后保留若干最重要的小波系数实现对直方图的模拟。当新的数据出现时,通过对这些小波系数进行更新以实现直方图的更新。 文献提出的实际上是一种直方图方法,只不过使用了小波变换。A. Gilbert等指出小波变换可以认为是信号与一组正交的长度为N的向量集合所作的内积,因此构造一组数据流数据的略图,由于略图可以相当容易和准确地计算信号与一组向量的内积,则可以从略图计算出小波系数,从而用于点查询和范围查询的估计。 新动向 研究人员对数据流处理的研究不断深入,我们认为出现了以下新的动向: 未来略图 引入更多多的的统计 计技技术来构造略图 G. Cormode等主要处理对频繁项的计算。它以前人的主项(majority item ) 算法([116, 117])为基础,使用了error-correcting codes来处理问题。如数据的每一位设立一个计数器,再根据这些计数器的计数结果来推断频繁项集合。 Y. Tao等[118]实质上是对Probabilistic counting (已经广泛地用于资料库领域的distinct counting)在数据流处理的一种套用。 扩展略图 对略图进行扩展,以处理更更复复杂的查询询需需求 Lin等在文献[93]中构造了一个复杂的略图体系,可用于滑动视窗模型(sliding window model )和n-of-N模型的分位数估计,这是简单略图难以做到的。 在滑动视窗模型下,文献[93]将数据按时间顺序分为多个桶,在每个桶中建立略图(精度比要求的高),然后查询时再将这些略图合并(merge),其中对最后一个桶可能需要进行提升(lift ) *** 作。维护时只删除过期的桶,增加新的桶。 在n-of-N model中,文献[93]将数据按EH Partitioning技术分为多个大小不同的桶,在每个桶中建立略图(精度比要求的高),然后查询时再将其中一部分略图合并,可以保证要求的精度,其中对最后一个同可能需要进行提升。 结合时空数据 与时空数据处理的进一步结合: J. Sun等在文献[120]中虽然主要针对时空数据的历史查询和预测处理。然而,文章却强调时空数据是以数据流的形式出现的,处理中也更着重于时空数据的更新性能。 Y. Tao等[118]使用数据流的方法处理时空数据,通过对动态的时空数据构造略图,用于分辨物体是否在多个区域间运动或静止的状态,并估算其数量。而这种问题在原先的时空处理中是很难解决的。 小说流派 网路小说数据流是新兴流派,意思是小说主角实力数据化,和网游属性栏一样的数据显示。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/sjk/6693618.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-03-26
下一篇 2023-03-26

发表评论

登录后才能评论

评论列表(0条)

保存