关于TDataSet和TFDJsonDataSets在处理数据库字段的字符集和执行效率方面的比较

关于TDataSet和TFDJsonDataSets

在处理数据库字段的字符集和执行效率方面的比较

一、TDataSet和TFDJsonDataSets内部处理字符集的转化

1、TDataSet   

    1.1、是delphi最底层的处理数据库相关数据集的组件:DB.Data.pas  ,再往上-> TComponent :System.Classes.pas

    1.2、它内部不存在任何与字符集的转化相关的对象,仅存在一个对第三方工具开发者的Oem转化接口:Translate(Src, Dest: PAnsiChar; ToOem: Boolean): Integer

2、TFDJsonDataSets

    1.1、是最底层基于TDataSet的FDAC(Fire Data Access Component)数据访问组件  ->FireDac.Comp.Client 

    1.2、它的DataSet属性对应的父类TFDAdaptedDataSet ( ->FireDac.Comp.Client ) 的父类是TFDDataSet ( ->FireDac.Comp.DataSet )

    1.3、而TFDDataSet存在一个称为Encoder: TFDEncoder的属性

    1.4、而TFDEncoder从属于FireDac的标准工具集 (->FireDac.Stan.Util)

    1.5、TFDEncoder中提供了类似的一系列字符集编码及其存取格式的转化:

           Encoding :TFDEncoding;

           function Encode(const AStr: String; ADestEncoding: TFDEncoding = ecDefault): TFDByteString;

           其中:FireDac.Stan.Intf进行初始化等定义,这些便是与字符集编码及其存取格式相关的对象:

             TFDEncoding = (ecDefault, ecUTF8, ecUTF16, ecANSI);
             TFDTextEndOfLine = (elDefault, elWindows, elPosix);

             ecDefault默认TFDDataSet为Unicode字符集编码

    1.6、因而,如果你采用是的是TFDJsonDataSets的数据存取方案,无论数据库字段内存保存为任何格式,经过写入到TFDJsonDataSets并返回结果集的前后,适配数据集TFDAdaptedDataSet的父类TFDDataSet的Encoder,已经默认为你做了Unicode的转化。

             你的APP,无论MSWindows还是POSIX,都不会出现类似 '????'或'?'的乱码出现!

二、TDataSet处理字符集编码格式

    1、取决于其调用和存取的第三方工具开发者的Oem格式。

    2、比如微软Sql server 2000,如果字符型字段类型是:

             varchar(n) :长度为 n 个字节的可变长度且 非Unicode的字符数据。

                     n 必须是一个介于 1 和 8,000 之间的数值。字节存储大小为:输入数据的字节的实际长度,而不是 n 个字节

             nvarchar(n) :包含 n 个字符的可变长度的 Unicode字符数据。

                     n 的值必须介于 1 与 4,000 之间。字节的存储大小是:所输入字符个数的两倍

    3、因而,如果你采用是的是TDataSet的数据存取方案,数据返回客户端后:

            3.1、MSWindows:操作系统已经为你处理了字符集的内存存取及显示问题(Windows.api用的是MultiByteToWideChar做的自动转换),你的APP,不会出现类似 '????'或'?'的乱码!

            3.2、POSIX:操作系统内部并不做任何自动转化,必须依赖数据源:

                3.2.1、如果TDataSet返回的数据字段类型是通过varchar来的数据源你的APP,永远会出现类似 '????'或'?'的乱码,无论你在客户端做怎样的转换,也无济于事!

                 比如下图中,红框字段item_spec (物品规格) :不是出现乱码?就是长度被截断:

procedure TfmxDictionary.getDatabaseFromServer(
  DatatableNameOfGetData:string;TotalRecCountOfGetData :Integer;
  BeginRecNoOfGetData,RecCountPerPageOfGetData:Integer;
  pSql,pSelectKey:string);
var ATask:ITask;  AEvent:TEvent; //ifFinishTask:Integer;
    ADataSet: TDataSet;
    AEncoding,AFieldValueEncoding:TEncoding;
begin
  btnListViewMainQuery_QueryOnContitions.Enabled:=false;
  Memo1.Lines.Clear;//:测试用TMemo
  //多任务处理预留: if arrayTasks[0].Status=TTaskStatus.Running then arrayTasks[0].Wait(INFINITE);
    //ifFinishTask:=0;
  AEvent:=TEvent.Create; //:创建事件总线对象
  //创建并启动(多线程池化的)任务多核多处理器并行(多路CPU或GPU同时工作,或单处理器多核同步) //APool:= TThreadPool.Create; //:任务POSIX不能带池化参数产生
  ATask:=TTask.Create(
  procedure
    var AtestFieldDefsCircle:Integer;
  begin
    TMonitor.Enter(ListViewMain,0); //self//多核多处理器环境,经常用uses system;
    try
      try
        //ADataSet:直接赋值,既不要创建也不要释放:
        if not Assigned(ADataSet) then //多核多处理器环境,单核环境或win32应用不存在此问题!
          ADataSet:=
          ClientModule1.ServerMethods1Client.getTFDDataSet(pSql,pSelectKey);
            //直接传参调用公用单元也可://getTDataSet_SrvMethGetTFDDataSet(pSql,pSelectKey,ADataSet);
        //:调用服务端方法: //:其中ADataSet处理方式很重要,
          //:否则会报invalid Pionter错误:if not Assigned(ADataSet) then
        while ADataSet.State=dsInactive do sleep(0);//:确保返回并激活数据集
        if ADataSet.Active=true then ADataSet.First;

        TEncoding.GetBufferEncoding( //:获取代码页字符集编码格式:
          ADataSet.FieldByName('item_spec').AsBytes,AEncoding );
        Memo1.Lines.Add('测试当前代码页字符集编码格式:'+AEncoding.EncodingName);
        Memo1.Lines.Add('测试第1笔物品规格全角转半角结果:'+ DbdToCbd(ADataSet.FieldByName('item_spec').AsString.Trim,'') );

        //如果要做全角转半角的转换,就不要一次性CopyDataSet,而改用Append逐笔:
        //FDMT_SQLiteMain.FieldDefs.Clear;
        //FDMT_SQLiteMain.FieldDefs:=ADataSet.FieldDefs;
        //FDMT_SQLiteMain.Open;
        //FDMT_SQLiteMain.CopyDataSet(ADataSet,[coStructure,coRestart, coAppend]);
        FDMT_SQLiteMain.FieldDefs.Clear;
        FDMT_SQLiteMain.FieldDefs:=ADataSet.FieldDefs;
        FDMT_SQLiteMain.Open;
        while not (ADataSet.Eof) do
        begin
          FDMT_SQLiteMain.Append;
          for AtestFieldDefsCircle:=0 to FDMT_SQLiteMain.FieldDefs.Count-1 do
          begin
            //FieldDefs赋值,并未赋值字段长度Size和精度Precision:
            FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].Size
              :=ADataSet.FieldDefs[AtestFieldDefsCircle].Size;
            FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].Precision
              :=ADataSet.FieldDefs[AtestFieldDefsCircle].Precision;
            if (FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].DataType=TFieldType.ftString)
              or (FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].DataType=TFieldType.ftWideString) then
            begin
            { //对于TDataSet而言,是由服务器端数据源的字段类型的编码格式决定的:
                //:无论客户端怎样做转换也无济于事:
              if AEncoding<>TEncoding.Unicode then
                FDMT_SQLiteMain.FieldByName(
                  FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].Name).AsString
                :=
                DbdToCbd( //:再做Unicode全角转半角
                  TEncoding.Unicode.GetString(
                    TEncoding.Convert(AEncoding,TEncoding.Unicode,
                      AEncoding.GetBytes( //:先按原编码逐个字节做Unicode转换
                        ADataSet.FieldByName(
                          FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].Name).AsString.Trim
                      ) )
                  )
                ,'不取消中文');
            end else
              FDMT_SQLiteMain.FieldByName(
                FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].Name).AsString
              :=
                  ADataSet.FieldByName(
                    FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].Name).AsString.Trim
                ;
            }
              //:转换字段类型无用://FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].DataType:=TFieldType.ftWideString;
              FDMT_SQLiteMain.FieldByName(
                FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].Name).AsString
              :=DbdToCbd(
                  ADataSet.FieldByName(
                    FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].Name).AsString.Trim
                ,'不取消中文')
                ;
            end else //:是字符类型就做DbdToCbd全角转半角转化,否则不转换:
              FDMT_SQLiteMain.FieldByName(
                FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].Name).AsString
              :=
                ADataSet.FieldByName(
                  FDMT_SQLiteMain.FieldDefs[AtestFieldDefsCircle].Name).AsString.Trim
                ;
          end;
          FDMT_SQLiteMain.Post;
          ADataSet.Next;
        end;
          //Memo1.Lines.Add('测试内存表TFDMemTable记录数:'+IntToStr(FDMT_SQLiteMain.RecordCount));
        FDMT_SQLiteMain.IndexFieldNames:=pSelectKey;//:设置内存表索引字段数值
        FDMT_SQLiteMain.IndexesActive:=true; //:激活内存表的索引
          //:不要动游标:TMonitor处于Lock锁定状态会有冲突:FDMT_SQLiteMain.Last; FDMT_SQLiteMain.First;//不像其基类TDataSet,TClientDataSet和内存表等继承类可以任意操纵Cursor,而且是双向的游标。
        //TListView加载数据或重新加载数据:ListViewMain:
        if FDMT_SQLiteMain.RecordCount>0 then
          UpdateMyListViewItem(ListViewMain);
        //:上面是任务的具体内容!
        AEvent.SetEvent;//:事件总线通知:任务结束!
      except        //万一异常,代码很重要 :
        TMonitor.Exit(ListViewMain); //self:监视器锁定TListView就可以,无需锁定整个窗体:
        btnListViewMainQuery_QueryOnContitions.Enabled:=true;
        AEvent.Free;//:出现异常释放:事件总线!
        raise Exception.Create('任务出错或取消任务啦');
        exit;       //:出现异常就不要再往下执行了!
      end;
    finally
      TMonitor.Exit(ListViewMain); //监视器解锁
      btnListViewMainQuery_QueryOnContitions.Enabled:=true;
      //AEvent.Free;
        //:切忌:将事件总线在try内释放AEvent.Free;
    end;
    //事件总线AEvent.SetEvent后:确认任务是否完成:
      //Assert(AEvent.WaitFor(15000)>TWaitResult.wrSignaled);
    //:调试时:确认任务结束时长的信息标记,即AEvent.SetEvent,然后:
    AEvent.Free; //:确认收到任务结束的信号标记即AEvent.SetEvent后再:释放事件总线对象
  end );
  //多任务管理接口://arrayTasks[0]:=ATask;//TTask.WaitForAll(arrayTasks);
    //if arrayTasks[0].Status=TTaskStatus.Running then
      //arrayTasks[0].Wait(INFINITE);
  ATask.Start;
  //需要解决中途取消任务,退出APP时AEvent及1个未知的内存泄漏:
  {
  if not (ATask.Status=TTaskStatus.Canceled) then
  begin
    while ATask.Status=TTaskStatus.Running do
      btnDownLoadClick(btnDownLoad); btnDownLoad.SetFocus;
    if ATask.Status=TTaskStatus.Completed then
      btnDownLoadClick(btnDownLoad); btnDownLoad.SetFocus;
  end;
  }
end;

                3.2.2、如果TDataSet返回的数据字段类型是通过nvarchar来的数据源你的APP,不会出现类似 '????'或'?'的乱码!

但:其并不会给你做全角半角的格式转化,需要你自己在客户端做写代码转化,否则显示效果会感觉很不好:

点 “开始搜索”后的结果:

上图中第一笔索引行的Text2 ,就是没有经过客户端代码进行全角半角的格式转化的

                                    其它的,是经过客户端代码进行全角半角的格式转化的

显示效果,截然不同。

                3.2.3、假如你还是想用TDataSet存取Varchar类型的字符字段,还有一个办法就是:

                    当Varchar字段的Collation属性设置为Latin_General时,insert或者update为汉字,汉字不能正常存储,显示为问号。
                    只有修改为collation属性为如下之一:
                    Chinese_PRC_Stroke_90_BIN
                    Chinese_PRC_90_CI_AS
                    同时,insert或者update,必须把汉字值使用unicode标示符进行转换:
                    例如,N'汉字',
                    INSERT INTO [dbo].[tbl_a]  ([bb])   VALUES (N'中间a')
                    varchar字段才能够正常存储并显示汉字!

三、TDataSet和TFDJsonDataSets处理数据集的优劣比较

1、关于字符集编码格式及其内存存取格式问题,如上所述

2、TDataSet能在客户端请求Rest服务器时,方便的使用过滤器,比如传递所需返回记录范围

      TFDJsonDataSets过滤器使用不方便,现目前没有找到好的方法。

3、返回空数据集到客户端:TDataSet不能会报错且无法将空数据集的字段定义回调给客户端;而TFDJsonDataSets可以。

4、效率(环境是数据库和应用服务器分布式架构):小量数据看不出明显差异,大量数据(比如40字段*50000记录以上的矩阵)不分页一次性获取数据,TDataSet的速度是TFDJsonDataSets的7~10倍。TFDJsonDataSets主要慢在服务端TFDJSONDataSetsWriter.ListAdd和客户端TFDJSONDataSetsReader.GetListValueByName这两个环节,客户端尤其如此。

      其根本原因在于:Json慢,Json不适合处理大量数据的。21世纪的主流数据存取,应当是流TStream,大数据就是靠流在传输数据:服务器Json转BJson(二进制的Json)后,回调客户端,在客户端进行BJson转回Json。

如有所需,请联系QQ:584798030

发布了61 篇原创文章 · 获赞 6 · 访问量 5583

猜你喜欢

转载自blog.csdn.net/pulledup/article/details/101151970