hive源码之explode函数

文章详细介绍了Hive中的UDTF(User-DefinedTable-GeneratingFunctions),特别是explode函数的用途和实现原理。通过源码分析,解释了如何处理array和map类型的数据,以及initialize、process和close方法的作用。文章鼓励读者基于这些理解尝试编写自己的UDTF函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、函数介绍

二、使用案例

三、源码分析

四、总结


一、函数介绍

UDTF(User-Defined Table-Generating Functions)是一进多出函数,如hive中的explode()、posexplode()函数。explode()函数可以将数组(array类型)的元素分隔成多行,或将映射(map类型)的元素分隔为多行和多列。工作中经常会用到这个函数,今天我们这次来分析下explode()函数源码。

二、使用案例

查询sql以及结果

select explode(array(1,2,3));

三、源码分析

一个自定义的UDTF函数需要继承GenericUDTF抽象类,且实现initialize()、 process()、close()(可选)方法。initialize()方法会被hive调用去通知UDTF函数将要接收到的参数类型。该UDTF必须返回一个与UDTF函数输出相对应的对象检查器。一旦initialize()方法被调用,hive将通过process()方法把一行行数据传给UDTF。在process()方法中,UDTF可以通过调用forward()方法将数据传给其他的operator。最后,当把所有的数据都处理完以后hive会调用close()方法。
由于需要继承GenericUDTF抽象类,我们先来看下GenericUDTF抽象类源码


/**
 * A Generic User-defined Table Generating Function (UDTF)
 *
 * Generates a variable number of output rows for a single input row. Useful for
 * explode(array)...
 */

public abstract class GenericUDTF {
  Collector collector = null;

  /**
   * Additionally setup GenericUDTF with MapredContext before initializing.
   * This is only called in runtime of MapRedTask.
   *
   * @param context context
   */
  public void configure(MapredContext mapredContext) {
  }

  public StructObjectInspector initialize(StructObjectInspector argOIs)
      throws UDFArgumentException {
    List<? extends StructField> inputFields = argOIs.getAllStructFieldRefs();
    ObjectInspector[] udtfInputOIs = new ObjectInspector[inputFields.size()];
    for (int i = 0; i < inputFields.size(); i++) {
      udtfInputOIs[i] = inputFields.get(i).getFieldObjectInspector();
    }
    return initialize(udtfInputOIs);
  }

  /**
   * Initialize this GenericUDTF. This will be called only once per instance.
   *
   * @param argOIs
   *          An array of ObjectInspectors for the arguments
   * @return A StructObjectInspector for output. The output struct represents a
   *         row of the table where the fields of the stuct are the columns. The
   *         field names are unimportant as they will be overridden by user
   *         supplied column aliases.
   */
  @Deprecated
  public StructObjectInspector initialize(ObjectInspector[] argOIs)
      throws UDFArgumentException {
    throw new IllegalStateException("Should not be called directly");
  }

  /**
   * Give a set of arguments for the UDTF to process.
   *
   * @param args
   *          object array of arguments
   */
  public abstract void process(Object[] args) throws HiveException;

  /**
   * Called to notify the UDTF that there are no more rows to process.
   * Clean up code or additional forward() calls can be made here.
   */
  public abstract void close() throws HiveException;

  /**
   * Associates a collector with this UDTF. Can't be specified in the
   * constructor as the UDTF may be initialized before the collector has been
   * constructed.
   *
   * @param collector
   */
  public final void setCollector(Collector collector) {
    this.collector = collector;
  }

  /**
   * Passes an output row to the collector.
   *
   * @param o
   * @throws HiveException
   */
  protected final void forward(Object o) throws HiveException {
    collector.collect(o);
  }

}
  1. initialize初始化:UDTF首先会调用initialize方法,此方法返回UDTF的返回行的信息(返回个数,类型,名称)。其实就是把上一个operator返回的对象检查器传给UDTF,因为object inspector 对象检查器中保存了数据的类型,initialize针对任务调一次。
  2. process:初始化完成后,会调用process方法,对传入的参数进行处理,可以通过forword()方法把结果写出。传入一行数据写出去多次,与mapreduce中的map方法很像,也是一行一行的数据传入,传入一行数据输出多行数据,process针对每行数据调用一次该方法。
  3. close:最后close()方法调用,对需要清理的方法进行清理,close()方法针对整个任务调一次。

了解GenericUDTF抽象类之后,我们再来看下explode函数源码。


/**
 * GenericUDTFExplode.
 *
 */
@Description(name = "explode",
    value = "_FUNC_(a) - separates the elements of array a into multiple rows,"
      + " or the elements of a map into multiple rows and columns ")
public class GenericUDTFExplode extends GenericUDTF {

  private transient ObjectInspector inputOI = null;
  @Override
  public void close() throws HiveException {
  }

  @Override
  public StructObjectInspector initialize(ObjectInspector[] args) throws UDFArgumentException {
    if (args.length != 1) {
      throw new UDFArgumentException("explode() takes only one argument");
    }

    ArrayList<String> fieldNames = new ArrayList<String>();
    ArrayList<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>();

    switch (args[0].getCategory()) {
    case LIST:
      inputOI = args[0];
      fieldNames.add("col");
      fieldOIs.add(((ListObjectInspector)inputOI).getListElementObjectInspector());
      break;
    case MAP:
      inputOI = args[0];
      fieldNames.add("key");
      fieldNames.add("value");
      fieldOIs.add(((MapObjectInspector)inputOI).getMapKeyObjectInspector());
      fieldOIs.add(((MapObjectInspector)inputOI).getMapValueObjectInspector());
      break;
    default:
      throw new UDFArgumentException("explode() takes an array or a map as a parameter");
    }

    return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames,
        fieldOIs);
  }

  private transient final Object[] forwardListObj = new Object[1];
  private transient final Object[] forwardMapObj = new Object[2];

  @Override
  public void process(Object[] o) throws HiveException {
    switch (inputOI.getCategory()) {
    case LIST:
      ListObjectInspector listOI = (ListObjectInspector)inputOI;
      List<?> list = listOI.getList(o[0]);
      if (list == null) {
        return;
      }
      for (Object r : list) {
        forwardListObj[0] = r;
        forward(forwardListObj);
      }
      break;
    case MAP:
      MapObjectInspector mapOI = (MapObjectInspector)inputOI;
      Map<?,?> map = mapOI.getMap(o[0]);
      if (map == null) {
        return;
      }
      for (Entry<?,?> r : map.entrySet()) {
        forwardMapObj[0] = r.getKey();
        forwardMapObj[1] = r.getValue();
        forward(forwardMapObj);
      }
      break;
    default:
      throw new TaskExecutionException("explode() can only operate on an array or a map");
    }
  }

  @Override
  public String toString() {
    return "explode";
  }
}

有了对GenericUDTF抽象类的理解,就不难理解explode函数的源码了。由于explode函数可以把array数组或者映射map类型转成对行或者多列,所以会对数据类型进行判断,然后分别处理。有了以上源码经验,就可以尝试下自己去写一个自定义UDTF了。

四、总结

不管是hive还是mapreduece,都是比较复杂的一整块,我们在学习的时候不妨“浅尝辄止”。比如我们在继承这个类的时候,只需要关心它能实现什么功能、我们需要处理什么业务逻辑,而不去“深究”为什么它可以实现这样的功能。

### HiveSQL 中 `EXPLODE` 函数的使用说明 #### 什么是 `EXPLODE`? `EXPLODE` 是一种表生成函数 (Table Generation Function),用于将复杂数据类型的字段(如数组或映射)拆分成多行。它能够将单个记录中的集合类型字段展开为多个独立的行,从而便于后续分析操作。 在 HiveQL 查询中,通常会配合 `LATERAL VIEW` 子句一起使用,以便于将原始表的数据与通过 `EXPLODE` 转换后的结果结合起来[^1]。 --- #### 基本语法 以下是 `EXPLODE` 的基本语法结构: ```sql SELECT ... FROM table_name LATERAL VIEW EXPLODE(column_array_or_map) exploded_table AS column_alias; ``` - **`column_array_or_map`**: 需要被展开的目标列,支持数组 (`ARRAY`) 或映射 (`MAP`) 类型。 - **`exploded_table`**: 展开后生成的结果集名称。 - **`column_alias`**: 新生成列的别名。 如果希望保留原表中未匹配到的空值,则可以在 `LATERAL VIEW` 后加上 `OUTER` 关键字[^4]。 --- #### 示例代码 假设有一张学生兴趣爱好表 `student_hobbies`,其结构如下: | id | name | hobbies | |----|---------|-----------------------| | 1 | Alice | ["Reading", "Swimming"] | | 2 | Bob | ["Gaming"] | | 3 | Charlie | NULL | ##### 示例 1:基础用法 下面的例子展示了如何利用 `EXPLODE` 将学生的兴趣爱好数组拆分为单独的兴趣项。 ```sql SELECT id, name, hobby FROM student_hobbies LATERAL VIEW EXPLODE(hobbies) exploded_hobbies AS hobby; ``` 执行结果将是: | id | name | hobby | |----|---------|------------| | 1 | Alice | Reading | | 1 | Alice | Swimming | | 2 | Bob | Gaming | 注意:对于第三条记录(Charlie),由于 `hobbies` 列为空值,因此不会出现在结果集中[^2]。 --- ##### 示例 2:带 `OUTER` 处理空值的情况 为了确保即使源数据中有空值也能返回对应的主键信息,可以加入 `OUTER` 参数。 ```sql SELECT id, name, hobby FROM student_hobbies LATERAL VIEW OUTER EXPLODE(hobbies) exploded_hobbies AS hobby; ``` 此时的结果将会包括所有学生的信息,即便他们的兴趣列表为空: | id | name | hobby | |----|---------|------------| | 1 | Alice | Reading | | 1 | Alice | Swimming | | 2 | Bob | Gaming | | 3 | Charlie | NULL | --- #### 进阶示例:结合其他函数 除了简单的数组分解外,还可以与其他内置函数联合使用来完成更复杂的任务。例如,在字符串统计场景下可借助 `POSEXPLODE` 实现位置索引附加的功能[^3]。 假设有这样一张单词频率分布表 `word_counts`: | word | counts | |-------------|----------------| | apple | {"a":2,"p":2} | | banana | {"b":1,"n":3} | 可以通过以下语句提取字母及其对应数量: ```sql SELECT word, letter, count_value FROM word_counts LATERAL VIEW POSEXPLODE(counts) exploded_letters AS letter, count_value; ``` 得到的结果形似于此: | word | letter | count_value | |--------|--------|-------------| | apple | a | 2 | | apple | p | 2 | | banana | b | 1 | | banana | n | 3 | --- ### 总结 `EXPLODE` 及其变体提供了强大的工具帮助开发者轻松处理嵌套结构化数据。无论是简单的一维数组还是带有额外元信息的复合对象都能很好地适配实际需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值