4、ABAP内表操作与性能优化:内表类型(STANDARD/SORTED/HASHED)、内表排序与二分查找、LOOP AT与AT NEW/ENDAT 分组处理
内表操作,可以说是ABAP开发里最家常便饭的事了。我做了十几年SAP项目,几乎每个程序都离不开它。但说实话,很多人写了几年代码,对内表的理解还停留在「能跑就行」的阶段。今天咱们就好好聊聊内表的那些门道。
4.1 内表类型:选对了,性能翻倍
ABAP有三种内表类型:STANDARD、SORTED和HASHED。每种都有自己的脾气秉性。选错了,程序跑起来就像老牛拉车;选对了,嗖嗖的。
4.1.1 STANDARD TABLE(标准表)
这是最常用的内表类型。它的特点是:
- 主键索引是线性的,系统会维护一个逻辑索引
- 可以按任意顺序添加记录
- 查找时默认是顺序查找,除非你手动排序后使用二分查找
我在项目中遇到过这样一个场景:有个报表要从物料主表里取几万条数据,然后逐条去匹配另一个内表。一开始用的就是标准表,结果跑了快10分钟还没出结果。后来一查,问题就出在查找方式上。
适用场景:数据量不大(几千条以内),或者需要频繁追加数据的情况。
DATA: lt_standard TYPE TABLE OF mara.
APPEND wa_mara TO lt_standard. " 直接追加,没问题
4.1.2 SORTED TABLE(排序表)
排序表,顾名思义,数据会按照主键自动排序。你插入一条记录,系统会自动把它放到正确的位置上。
- 主键是排序字段,系统自动维护排序顺序
- 查找时自动使用二分查找,速度很快
- 插入和删除操作相对慢一些,因为要维护排序
我个人习惯在需要频繁按主键查找的场景下使用排序表。比如做数据校验时,用排序表比标准表快得多。
小技巧:如果你确定数据量很大(上万条),而且主要操作是读取而不是写入,排序表是首选。
DATA: lt_sorted TYPE SORTED TABLE OF mara WITH UNIQUE KEY matnr.
READ TABLE lt_sorted INTO wa_mara WITH KEY matnr = '100-100'. " 自动二分查找
4.1.3 HASHED TABLE(哈希表)
哈希表是性能怪兽。它通过哈希算法直接定位数据,理论上查找速度是O(1)。但代价是:
- 必须定义唯一主键
- 不能按非主键字段排序或查找
- 内存占用比排序表大
我曾经在一个接口程序里,需要根据物料号快速查找对应的描述信息。数据量大概20万条,用标准表查一次要好几秒,换成哈希表后,基本感觉不到延迟。
注意:哈希表只能按完整主键查找。如果你需要按非主键字段查找,或者需要范围查找,哈希表就不合适了。
DATA: lt_hashed TYPE HASHED TABLE OF makt WITH UNIQUE KEY matnr.
READ TABLE lt_hashed INTO wa_makt WITH TABLE KEY matnr = '100-100'. " 必须用TABLE KEY
4.2 内表排序与二分查找
说到性能优化,排序和二分查找是绕不开的话题。很多新手写代码,上来就是LOOP套LOOP,数据量一大就崩了。
4.2.1 为什么要排序?
标准表默认是顺序查找。什么意思呢?就是你要找一条数据,系统会从第一条开始,一条一条往下比。如果数据在最后一条,那就得比完所有记录。数据量小还好,数据量大了,这就是灾难。
二分查找就不一样了。它每次把数据分成两半,判断目标在哪一半,然后继续分。比如100万条数据,最多20次比较就能找到。你想想看,这差距有多大?
4.2.2 二分查找的正确姿势
使用二分查找有两个前提:
- 内表必须按查找字段排序
- 查找字段必须是排序字段的一部分
" 错误示范:没排序就二分查找
READ TABLE lt_standard INTO wa_data WITH KEY matnr = '100-100' BINARY SEARCH.
" 结果:可能找不到,或者找到错误的数据
" 正确做法:先排序,再二分
SORT lt_standard BY matnr.
READ TABLE lt_standard INTO wa_data WITH KEY matnr = '100-100' BINARY SEARCH.
我记得有一次帮同事排查性能问题,他的程序跑了半小时还没完。我一看代码,好家伙,在LOOP里用了READ TABLE,但没加BINARY SEARCH。数据量也就5万条,但LOOP了5000次,每次都是顺序查找。改成二分查找后,程序30秒就跑完了。
性能对比:
| 数据量 | 顺序查找(平均) | 二分查找(平均) |
|---|---|---|
| 1,000 | 500次比较 | 10次比较 |
| 10,000 | 5,000次比较 | 14次比较 |
| 100,000 | 50,000次比较 | 17次比较 |
4.3 LOOP AT与AT NEW/ENDAT 分组处理
分组处理是ABAP里一个很实用的功能。说白了,就是按某个字段的值,把数据分成一组一组的,然后对每组进行处理。
4.3.1 AT NEW / ENDAT 的基本用法
这个语法要求内表必须按分组字段排序。系统会在分组字段值变化时触发AT NEW,在下一组开始时触发AT ENDAT。
SORT lt_data BY werks matnr. " 必须先排序
LOOP AT lt_data INTO wa_data.
AT NEW werks.
WRITE: / '工厂:', wa_data-werks.
CLEAR: gv_total.
ENDAT.
gv_total = gv_total + wa_data-menge.
AT END OF werks.
WRITE: / '总数量:', gv_total.
ENDAT.
ENDLOOP.
嗯,这里要注意:AT NEW和AT ENDAT的触发时机。AT NEW是在分组字段值变化时触发,也就是新一组的第一条记录。AT ENDAT是在当前组的最后一条记录处理完后触发。
4.3.2 常见的坑
我曾经在项目里看到过这样的代码:
" 错误:没有排序就使用AT NEW
LOOP AT lt_data INTO wa_data.
AT NEW werks. " 结果:每个工厂都会出现多次
...
ENDAT.
ENDLOOP.
如果不排序,AT NEW会在每次字段值变化时触发。但因为没有排序,相同工厂的数据可能分散在各处,导致AT NEW被多次触发。这显然不是我们想要的结果。
我的建议:使用AT NEW/ENDAT之前,一定要先按分组字段排序。这是铁律,别偷懒。
4.3.3 分组处理的性能考量
分组处理比在LOOP里自己写IF判断要高效得多。为什么?因为AT NEW/ENDAT是系统级别的优化,它直接利用了内表的排序特性,不需要额外的比较操作。
我一般会在以下场景使用分组处理:
- 按工厂汇总物料数量
- 按采购订单分组显示行项目
- 按客户分组计算销售总额
说白了,只要是需要按某个字段做汇总或分组输出的,AT NEW/ENDAT都是首选方案。
4.4 性能优化实战建议
最后,我总结几条实战经验:
- 选对内表类型:频繁读取用SORTED或HASHED,频繁写入用STANDARD
- 能用二分查找就别顺序查找:数据量超过1000条,二分查找的优势就很明显了
- 分组处理前一定要排序:这是新手最容易犯的错误
- 避免在LOOP里嵌套LOOP:能用READ TABLE就用,别偷懒写内循环
- 善用COLLECT:做汇总时,COLLECT比LOOP+ADD快得多
我记得有一次优化一个月结报表程序,就是把一个嵌套了3层LOOP的代码,改成了排序+二分查找+分组处理。结果运行时间从45分钟降到了3分钟。你想想看,这优化效果有多明显。
好了,内表操作这块就聊到这儿。下一章咱们讲讲ABAP中更高级的数据处理技巧,比如字符串操作和正则表达式。这些东西在实际项目中用得好,能省不少事。