怀旧

  不知道什么时候开始,八零后的人开始喜欢怀旧。
  他们是生活在变革夹层期的一代。
  对于七十年代的房子,他们错过了分配。
  对于九十年代的商品楼市,他们大多数人的收入基本无缘。
  于是他们开始作起了房奴。抑或是啃老族。
  当然,这是在大多的社会资源还掌握在50-60人的手中的时候,
  
  很多出生平常的八零后,只能靠自己的努力在杂草丛生的社会里面摸爬滚打,
  他们怀念旧过去,因为在他们身上有上一辈人的淳朴。
  他们渴望新事物,因为在他们周围是一个多色的社会。
  
  在推翻了封建思想,同时摒弃大多古老的文化的今天,学校教会他们信仰科学。
    小时的他们都有一个美好的梦想,
    单纯的做一个医生,一个老师,一个科学家,为人民服务为社会贡献力量,
    也许那时候他们还跟本不懂得生活原比他们想的要复杂的多。
  
  来到了社会,他们发现有时连人们所说的科学都不再“科学”了,
  
  医生开始杀人,
  学校开始盈利,
  肉体成为商品,
  感情成为游戏,
  桑拿成为时尚,
  教授成为商人。
  
  于是他们时常感到迷茫,原来的一切都变了,变得陌生,变得难以接受。
  没有信仰,没有责任感,但是存在一种怎样的生存压力?
  
  很多时候他们想回到过去,回到那个单纯而又简单的童年,
  于是他们开始怀旧,原来的那一切感觉是如此的亲切,如此的温馨。
  而又不得不回到冰冷的现在,就好像这无情的雪,美却没有一丝好感,一丝温馨。
  
  都说人的适应能力是很强的,没有能力改变,只有努力的去适应。
  很多人开始不断的寻觅,很多还保留着读书的习惯,
    希望从文字堆中能找到他们的信仰,
  找到他们所为之奋斗的目标,找到他们的精神依靠。
  
  很多的人却是在迷失,堕落,随着岁月和金钱流逝,腐化在了社会里,
    渐渐的糜烂,变成鬼,变成魔,变得人不人鬼不鬼。
  
  有人说:人的灵魂注定是孤独的。
  他们总是在夜深人静的时候思绪万千,舔舐伤口。
  社会这个圈很大,只要一天还生活在这个社会,就还没有跳出这个怪圈。
  为了不被淹没,他总在告诫自己,
    不论将来变成什么样,永远保住最后那块自留地的清澈。

剧未终,人已散!

  别人都说曲终人才散,其实曲子还没有弹到一半,就已经开始散场了,
  理由是剧院没有那么多的钱来交电费,所以不得不关闭一些灯管,
  并且关闭一些区域,很是无奈,不平,愤懑。

  当初要是没有那么多预算,就不应该招那么多的人来看电影,
  大雪天的,别人抽出了差不多整个黄金周末里面最宝贵的时间来到剧院,
  满心欢喜的准备观看一场大戏,他们都是学艺术的,
  对于艺术,他们都有一个梦想,
  希望有一天能在舞台上一展身手,抑或是找一个共鸣,于是他们聚集到了一起。
  他们没有上流雄厚的社会的资本,可以找一个十分富丽的国家大剧院开始享受。

  于是他们选择了这里,
  当看着离去的无奈和义无反顾,
  我渐渐的发现,我们的选择,错了。
  这里的东西并不是我们想要的,
  或许说这里并没有我们想要的东西。
  有些时候问,你终究想要什么?

  其实只不过是一个衣食无忧的简单生活而已。
  可惜确实很难,
  继续挣扎在贫困线上,
  生活或许本来就是这个样子的。
  
  
  

c#里面的异步调用

  最近需要在b/s和c/s下面进行对数据库的异步调用,因为有些查询数据库的操作耗时比较大,所以需要,采用异步,或者线程来操作,要不然用户就得等死,为了不让别人等死,所以开始下面的正文。
  
  使用线程来对程序进行操作,但是发现线程需要用好多timer来监听,如果多的话很麻烦,后来发现用异步调用比较好,只要程序结束,结果自动返回。而且实际上也是开了一个后台的线程来进行操作的,感觉有些场合操作起来比线程要实用得多,今天看了下例程,写了点自己的小心得吧。
  
  对于异步调用,用百度搜索,资料绝对是一大堆,但是追其本质,还是在IAsyncResult这个接口的辅助,以及华丽的委托上面。
  
  委托支持同步和异步调用。在同步调用中,一个委托的实例可记录多个目标方法;在异步调用中,一个委托实例中有且只能包含一个目标方法。异步调用使用委托实例的BeginInvoke方法和EndInvoke方法分别开始调用和检索返回值,这两个方法在编译期生成。调用BeginInvoke后委托立即返回;调用EndInvoke时倘若委托方法未执行完毕,则阻塞当前线程至调用完毕。
  
  再来看IAsyncResult这个接口,
  // Properties
   object AsyncState { get; }
   WaitHandle AsyncWaitHandle { get; }
   bool CompletedSynchronously { get; }
   bool IsCompleted { get; }
  这四个属性用来显示异步调用线程里面的状态,其内部还是开辟了一个线程而回传一个句柄,这个句柄用来监视线程的执行。
  
  要转化同步方法为异步方法,我们这里借助了委托,所以首先要声明一个回调的委托类型;
  
  比如你要调用的方法是这样的,
  public string myfunction(int value);
  那么你的委托当然应该写成这样
  Public delegate string MyFunctionDelegate(int value);
  
  然后开始在需要异步调用的地方开始利用IAsyncResult接口来监听线程的执行。实际上异步调用的关键还是在于委托,大家都知道委托相当于一个函数指针,里面可以订阅相同类型的实际方法,但是委托提供了beginInvoke方法来对所订阅的方法采取异步的方式调用,而且借助IAsyncResult可以获得异步方法调用的句柄,采用endInvoke可以获得异步调用的返回结果。
  
  委托声明完毕,那么下面就开始执行异步调用的具体方式,这里的异步调用大致可以分成5种比较细小的类型,他们的不同之处关键还是在于对异步方法的监听上面,
  
  首先构造委托对象,封装你自己所需要的方法,我这里简单,方法里面就一句话
  方法:
  public string Myfucntion(int value)
  {
   Console.WriteLine(“我在等待,” + value.ToString());
   return “我做完了!”;
  }
  委托订阅方法:
  MyFunctionDelegate mydelegate = new MyFunctionDelegate(Myfucntion);
  
  上面二步做完以后就可以开始异步了:
  首先是一直等方式:
  这里有2种实现方法,反正都是一直等,
  Int value=10;
  String result=0;
  IAsyncResult aResult = mydelegate.BeginInvoke(value, null, null);
  //做你其他想做的,这里面代码肯是是写死的,所以做什么只有在编程时候预定好,界面上、、//面还是锁死的。
  result = mydelegate.EndInvoke(aResult);
  这样一直等的第一种方式就实现了,
  下面是一直等的第二种方式,
  不同的地方就是在BeginInvoke和EndInvoke中间放是,
  aResult.AsyncWaitHandle.WaitOne();
  这里代表的是主线程阻塞,就代表什么都不做,死等,要是方法调用时间很长,比如说10年,那么你准备等老吧,哈哈。
  
  为了不让你等老,下面有两种可以不同等的方法,就是在执行异步调用的时候,可以让你在界面上还做其他的事情。同样还是在中间写
  while(aResult.IsCompleted == false)
  {
   Thread.Sleep (10) ;
   Application.DoEvents();
  }
  当然上面的doevent是只有在system.form里面才有的。
  
  最后一种方式是比较高级的方式,就是在异步调用操作结束以后就可以得到返回值,这里需要借助一个委托来获取endInvoke的方法,这个委托是AsyncCallback,写在Begininvoke中,所以异步调用的代码就编程为.
  AsyncCallback sss=new AsyncCallback(myAsyncCallback);
  IAsyncResult aResult = mydelegate.BeginInvoke(value, sss, null);
  回调函数
  Public void myAsyncCallback(IAsyncResult ar)
  {
   String s;
   MyFunctionDelegate mar=( MyFunctionDelegate)ar.AsyncState;
  s = mar.EndInvoke (ar) ;
  }
  
  到这里基本的异步调用的集中方式就介绍完毕了。后面的是稍微的一点扩充,就是在asp.net里面采用异步调用的页面,
  
  在asp.net里面,ASP.net2.0提供了十分方便的机制来调用异步页面,
  
  首先要在页面的 @ Page 指令中添加如下的 Async=”true” 的属性
  究其本质,这段代码的作用是告诉 ASP.NET 在页面中执行 IHttpAsyncHandler。接下来,您需要在页面生存期的早期(例如,在 Page_Load 期间)调用新的 Page.AddOnPreRenderCompleteAsync 方法,以注册一个 Begin 方法和一个 End 方法,如以下代码所示:
  AddOnPreRenderCompleteAsync (
   new BeginEventHandler(MyBeginMethod),
   new EndEventHandler (MyEndMethod)
  );
  Begin 方法还会返回一个 IAsyncResult,它能够让 ASP.NET 确定何时完成异步操作,以便 ASP.NET 能够在这一时刻从线程池提取线程并调用 End 方法。当 End 返回后,ASP.NET 执行包括呈现阶段在内的页面生存期的剩余部分。在 Begin 返回后与 End 被调用前的这段时间内,处理请求的线程处于空闲状态,可以为其他请求提供服务,直到 End 被调用,呈现被显示
  protected void Page_Load(object sender, EventArgs e)
   {
   if (!IsPostBack)
   {
   // Hook PreRenderComplete event for data binding
   this.PreRenderComplete +=
   new EventHandler(Page_PreRenderComplete);
  
   // Register async methods
   AddOnPreRenderCompleteAsync(
   new BeginEventHandler(BeginAsyncOperation),
   new EndEventHandler(EndAsyncOperation)
   );
   }
   }
  IAsyncResult BeginAsyncOperation (object sender, EventArgs e,
   AsyncCallback cb, object state)
   {
   string connect = WebConfigurationManager.ConnectionStrings
   [“PubsConnectionString”].ConnectionString;
   _connection = new SqlConnection(connect);
   _connection.Open();
   _command = new SqlCommand(
   “SELECT title_id, title, price FROM titles”, _connection);
   return _command.BeginExecuteReader (cb, state);
   }
  
   void EndAsyncOperation(IAsyncResult ar)
   {
   _reader = _command.EndExecuteReader(ar);
   }
  protected void Page_PreRenderComplete(object sender, EventArgs e)
   {
   Output.DataSource = _reader;
   Output.DataBind();
   }
  
   public override void Dispose()
   {
   if (_connection != null) _connection.Close();
   base.Dispose();
   }
  
  

另类MDX学习记要

  本文作者:donegal 转载请注明出处:murdercdh.tianya.cn
  有错误的地方请给我E-Mail:murdercdh@126.com
  
开篇
  对于数据挖掘与商业智能这一块内容,Sql Server提供了一整套的解决方案,本着易用,快速的原则,将大部分东西都集成到了SSAS当中,作为中小企业实施BI首选。
 在这里我想重要讲解的是在SSAS当中非常重要的一个环节,MDX语言。
  学习新的技术,就好像拿到了一本新的武功图谱,过程总是充满激情,思维的互相碰撞,使整个过程充满悬念,当然可能长时间修炼可能造成枯燥,厌恶,但是为了成为一代大侠,这个过程是原始的积累和学习过程,不可跳跃,除非你是自创武功,独成一派,这样的功力至少需要差不多几十年的积累,张三疯不是一百岁才创太极么?所以各位安身修炼,切不可急躁。
  
总诀式
  首先给出定义:多维表达式 (MDX) 是一种功能完备、基于语句的脚本语言,用于定义、使用以及从 Microsoft SQL Server 2005 Analysis Services (SSAS) 中的多维对象中检索数据。
  MDX 提供以下几种语言功能:
  1. 用于创建、删除以及使用多维对象的数据定义语言 (DDL) 语句。
  2. 用于从多维对象中检索操作数据的数据操作语言 (DML) 语句。
  3. 用于管理作用域、上下文以及 MDX 脚本内的流控制的脚本语言语句。
  4. 用于操作从多维对象中检索的数据的大量运算符和函数。
  要想深入的了解这种语言所带来的巨大优势和遍历,首先需要对整个SSAS的框架体系做一个大致的了解,这样才能更加深入的了解MDX的强大功能以及复杂程度。
  Microsoft SQL Server 2005 Analysis Services (SSAS) 使用服务器组件和客户端组件为商业智能应用程序提供联机分析处理 (OLAP) 和数据挖掘功能:
  
六大心法秘诀:练功者必须仔细体会,待小有所成,亦可不断体会,必有所得。
  
特性1:Analysis Services 的服务器组件作为 Microsoft Windows 服务来实现。SQL Server 2005 Analysis Services 支持同一台计算机中的多个实例,每个 Analysis Services 实例作为单独的 Windows 服务实例来实现。
  
  特性2:客户端使用公用标准 XML for Analysis (XMLA) 与 Analysis Services 进行通信,XMLA 是一个基于 SOAP 的协议,用于发出命令和接收响应,公开为一项 Web 服务。此外,客户端对象模型通过 XMLA(包括托管提供程序 (ADOMD.Net) 和本机 OLE DB 访问接口)进行提供。
  
  特性3:查询命令可使用下列方式发出:SQL;多维表达式 (MDX)(一种面向分析的行业标准查询语言);或数据挖掘扩展插件 (DMX)(一种面向数据挖掘的行业标准查询语言)。还可以使用 Analysis Services 脚本语言 (ASSL) 来管理 Analysis Services 数据库对象。
  
  特性4:Microsoft SQL Server 2005 Analysis Services (SSAS) 支持瘦客户端体系结构。Analysis Services 计算引擎完全基于服务器,因此,所有查询都在服务器上进行解析。因此,每个查询只需在客户端和服务器之间进行一次来回行程,从而使得性能可以随着查询复杂性的增加而伸缩。
  
  特性5:Analysis Services 的本机协议为 XML for Analysis (XML/A)。Analysis Services 为客户端应用程序提供了数个数据访问接口,但是所有这些组件都使用 XML for Analysis 与 Analysis Services 实例进行通信。
  
  特性6:Analysis Services 提供了数个不同的访问接口,以支持不同的编程语言。访问接口借助 Internet 信息服务 (IIS),并通过 TCP/IP 或 HTTP 发送和接收 SOAP 数据包中的 XML for Analysis 来与 Analysis Services 服务器进行通信。HTTP 连接使用由 IIS 实例化的 COM 对象(称为数据抽取),该对象充当 Analysis Services 数据的管道。数据抽取既不会以任何方式检查包含在 HTTP 流中的基础数据,也不会检查可用于数据库本身中任何代码的任何基础数据结构。
  
  对于ssas有了一个大体的了解以后,就可以开始对具体的mdx语言开始进行学习了。对于一些基础的概念,会穿插到里面进行解释,这样大家就比较容易理解。
  
  下面介绍本门武功的基础概念,这样将有住于练习者领会其中内容,切记,基础乃练功之根本,务必正确领会,否则就像当年梅超疯理解偏差,错炼九鹰白骨爪,终究难成正果。
  下面用一个cube例子来解释基础的语言和概念:
  关系数据库以二维平面表的形式组织数据。这些表有一个列维度和一个行维度。在每个行和列的交点处只有一个数据元素。
  而多维数据库则不同,它是基于称为“多维数据集”的结构,如下图所示。多维数据集按“层次结构”组织数据,而不是以表的形式组织数据。
  
  成员:
  成员是维度中的一个项目,表示数据的一次或多次出现。可将维度中的成员看作基础数据库中的一个或多个记录,该记录在此列中的值属于此类别。成员是描述多维数据集中的单元数据时的最低级别的引用。
  可以用成员名称或成员键引用某个成员。在上一示例中,用成员在 Time 维度中的名称 4th quarter 来引用该成员。但是,如果维度不具有非唯一的成员名称,则成员名称可以重复,也可以更改渐变维度中的成员名称。
  引用成员的另一种方法是引用成员键。维度使用成员键明确标识特定成员。在 MDX 中,“与”符号 (&) 用于区分成员键和成员名称。例如,以下引用使用 4th quarter 成员的成员键 Q4:
  [Time].[2nd half].&[Q4]
  
  元组:
  包含在多维数据集中的数据元素称为“单元”。通过对多维数据集中包含的每个属性层次结构指定一个成员可以唯一地标识一个单元。标识一个单元的属性的组合称为“元组”。
  元组标识多维数据集中的单元。一个元组由多维数据集中每个层次结构中的一个成员组成(显式或隐式引用)。如果特定层次结构中的成员没有在元组中显式引用,则该层次结构中的默认成员将隐式包含在元组中。
  在 MDX 中,元组根据其复杂性依照语法进行构造。如果元组只由一个层次结构中的一个成员组成(通常称为“简单元组”),则下列语法是可以接受的:
  Time.[2nd half]
  例如,下面的元组标识了上图中值为 240 的一个单元(因为这里有四个维度,所以四维定义一个元组):
  ( Source.[Eastern Hemisphere].Africa,
  Time.[2nd half].[4th quarter],
   Route.Air,
   Measures.Packages)
  
  正如可以指定从关系数据库的表中检索多组列或行一样,您可以指定从多维数据集中检索一组元组。MDX 中用来指一个有序的元组集合的标识符称为“集”。下面的示例标识了上图所示的多维数据集中的一个元组集:
  
  { (Time.[1st half].[1st quarter]),
   Time.[2nd half].[3rd quarter]) }
  
  集:
  集是零个、一个或多个元组的有序集合。集最常用于定义 MDX 查询中的查询轴和切片器轴,因此可以只有一个元组,在某些情况下,也可以为空。下面的示例显示了具有两个元组的集:
  { (Time.[1st half], Route.nonground.air) , (Time.[ 2nd half], Route.nonground.sea) }
  好了,了解完了这些基本的概念以后就可以正式开始使用mdx语句来获取你想要的数据了,
  
  具体的语句看起来和sql的语句差不多,查询的思路也差不多,但是所有的数据都要以上面的概念去理解,而不是简单的一维度和二维度的,而是多维的,所以要用集合,元组,成员这些概念去理解,一开始接触的人理解起来可能会比较困难,不过慢慢的就可以加深理解了。
  
  下面我们就用几条比较经典的语句来摡略的学习整个mdx的语法,这样的学习方式可能会遗漏许多细节的地方,但是对于快速入门,那是绝对有好处的,在使用熟练度达到一定要求以后,就可以查阅sdk对一些细节的地方进行处理。
  
  内功心法和总诀式这里就介绍完毕了,下面开始具体的招数,每一招分为许多层次,由简单到复杂,切不可急功近利,一定要着重招式的基础部分的领悟,否则很容易走火入魔。
  
  第一式:查询语句
  第一层:
  SELECT
   { Route.nonground.Members } ON COLUMNS,
   { Time.[1st half].Members } ON ROWS
  FROM TestCube
  
  这条语句很简单,就是从Adventure Works里面查询出mesaures维度下的members成员,所查询出来的属性集作为列,而下面的这个Product.Style.CHILDREN属性集作为行。
  
  提示:这里存在三个细节扩展需要注意:
  第一个是成员的限定,可以在 MDX 查询中使用 WITH 关键字
  第二个是成员的的函数,可用于检索其他 MDX 实体(如维度和级别)中的成员
  第三个是查询轴内容和切片器轴内容的查询:
  具体的在大家深入以后就会有一个了解。
  第二层:
  SELECT
   [Measures].[Special Discount] on COLUMNS,
   NON EMPTY [Product].[Product].MEMBERS ON Rows
  FROM [Adventure Works]
  WHERE [Product].[Category].[Bikes]
  
  这里多加了一个where语句,看似和sql语句差不多,但是理念是不一样的,需要了解下面二个概念:
  查询轴:查询轴用于指定由多维表达式 (MDX) SELECT 语句所返回的单元集的范围。通过指定单元集的范围可以限定客户端可以看到的返回数据。
  切片器轴:切片器轴将对多维表达式 (MDX) SELECT 语句返回的数据进行筛选,限定返回的数据,从而只返回与指定成员相关的数据。切片器轴是在 MDX 中 SELECT 语句的 WHERE 子句中定义的
  每个 MDX 查询都在指定的多维数据集上下文中执行。此上下文定义了由该查询中的表达式求值的成员。
  在 SELECT 语句中,FROM 子句用于确定多维数据集上下文。此上下文可以是整个多维数据集,也可以只是该多维数据集的一个子多维数据集。如果通过 FROM 子句指定了多维数据集上下文,就可以使用其他函数来扩展或限制该上下文。
  
  第三层:
  WITH SET [ChardonnayChablis] AS
   ‘Filter([Product].Members, (InStr(1, [Product].CurrentMember.Name, “chardonnay”) <> 0) OR (InStr(1, [Product].CurrentMember.Name, “chablis”) <> 0))’
  SELECT
   [ChardonnayChablis] ON COLUMNS,
   {Measures.[Unit Sales]} ON ROWS
  FROM Sales
  
  第四层:
  create Session set [Store].[SetCities_2_3] as
  {[Data Stores].[ByLocation].[State].&[CA].&[City 02],
  [Data Stores].[ByLocation].[State].&[NH].&[City 03]}
  这里的第三层和第四层属于同一内容的两个不同方面,应该联系起来学习相互对照,这样可以对这个招术有一个比较深刻的记忆和理解。
  查询作用域:若要创建一个命名集,该命名集被定义为 MDX 查询的一部分并且其作用域因此被限制在该查询内,请使用 WITH 关键字。然后,就可以在 MDX SELECT 语句中使用该命名集。通过这种方法,更改用 WITH 关键字创建的命名集时就不会打乱 SELECT 语句。
  会话作用域:若要创建一个命名集,使其作用域比查询上下文更广(即,其作用域为 MDX 会话的生存期),请使用 CREATE SET 语句。使用 CREATE SET 语句定义的命名集对该会话中的所有 MDX 查询均可用。例如,CREATE SET 语句对于需要在多种查询中大量重用某个集的客户端应用程序会非常有用。
  
第五层:
  WITH
   MEMBER [Measures].[Special Discount] AS
   [Measures].[Discount Amount] * 1.5
  SELECT
   [Measures].[Special Discount] on COLUMNS,
   NON EMPTY [Product].[Product].MEMBERS ON Rows
  FROM [Adventure Works]
  WHERE [Product].[Category].[Bikes]
  这里糅合了前面几层的东西,但是增加了表达式的计算元素,修炼者需要掌握前面级别,方可轻松领悟这一层次。
  这里的on colums和on rows的限定,可以用axis函数来代替,他们所表达的意义是相同的,在axis当中,以下数字分别代表不同的维度界定。
  0 Columns
  1 Rows
  2 Pages
  3 Chapters
  4 Sections
  这里需要注意的是如果要采用1,那么必须采用0,如果要采用3,那么必须采用了0,1和2,这里的维度是逐级构造的,不能跳跃。
  
第六层:
  Create Session Member [Store].[Measures].LastFourStores as
  sum(([Stores].[ByLocation].Lag(3) :
  [Stores].[ByLocation].NextMember), [Measures].[Units Sold])
  采用内部成员属性:定义内部成员数次女冠以供使用.
  SELECT
   CROSSJOIN([Ship Date].[Calendar].[Calendar Year].Members,
   [Measures].[Sales Amount]) ON COLUMNS,
   NON EMPTY Product.Product.MEMBERS
   DIMENSION PROPERTIES
   Product.Product.[List Price],
  
   Product.Product.[Dealer Price] ON ROWS
  FROM [Adventure Works]
  WHERE ([Date].[Month of Year].[January])
  这里写了二条语句,第一条的特殊之处在于使用了功能函数对数据成员进行了操作,而后面的一条带cross join的语句记忆后面的DIMENSION PROPERTIES
  ,涉及到了高级招式的一些细节,在这里不推荐大家练习,待练到后面的招式,这一招自然会融会贯通,切不可操之过急。
  
  小结:
  从以上的几个语句,大家可以很好的看到mdx语句以及他的特性,很多细节的东西都在里面了,整个过程是一个由简单到复杂,由主题到细节的过程,具体的还得大家在实际运用中慢慢体会。
  Ps:第一式里面的层级就只有六层,一次修炼,不断演练,可达到熟练运用的境界,对于本心法,实战当中用的最多的可能就是第一式里面的东西,所以各位修练者务必做到能构熟练运用。
   
  第二式:功能函数
  这一层的描述,包含了许多细节的功能函数,这些函数对于在聚合,整合数据的时候起到很大的作用。
  第一层:
  SELECT Measures.[Internet Sales Amount] ON COLUMNS,
   CrossJoin ( {Product.[Product Line].[Product Line].MEMBERS},
   {[Customer].[Country].MEMBERS}) on ROWS
   FROM [Adventure Works]
  这里添加了一个特殊的CrossJoin,其实是对里面的两个成员组的做笛卡尔集,之后再聚合数据。做笛卡尔集的时候有些单元可能是空的,所以有了后面的招数,负责清空空的]] >