UDF开发手册 - UDTF

2,767 阅读3分钟

表生产函数

一行输入,输出多行结果

实现 自定义UDTF 需要继承 GenericUDTF,并且实现其三个方法:

  • initialize()
  • process()
  • close()

其中 process()、close() 为 GenericUDTF 中的抽象方法,必须实现。initialize() 虽然不是抽象方法,但必须手动覆盖实现该方法,因为 GenericUDTF 的 initialize() 最终会抛出一个异常:

throw new IllegalStateException("Should not be called directly");

initialize()

需要覆盖实现的方法如下:

public StructObjectInspector initialize(StructObjectInspector argOIs)
  throws UDFArgumentException { }

initialize() 在函数在 GenericUDTF 初始化时被调用一次,执行一些初始化操作,包括:

  1. 判断函数参数个数
  2. 判断函数参数类型
  3. 确定函数返回值类型

除此之外,用户在这里还可以做一些自定义的初始化操作,比如初始化HDFS客户端等

其一:判断函数参数个数

initialize() 的参数为 StructObjectInspector argOIs

可以通过如下方式获取 自定义UDTF 的所有参数

List<? extends StructField> inputFieldRef = argOIs.getAllStructFieldRefs();

判断参数个数的方式很简单,只要判断 inputFieldRef 的元素个数即可

示例:

List<? extends StructField> inputFieldRef = argOIs.getAllStructFieldRefs();
// 参数个数为1
if (inputFieldRef.size() != 1)
    throw new UDFArgumentException("需要一个参数");

其二:判断函数参数类型

inputFieldRef 的元素类型是 StructField,可以通过 StructField 获取参数类型 ObjectInspector

ObjectInspector 类型的判断方法可以参考 《UDF开发手册 - UDF

判断参数个数和类型的示例:

// 1. 判断参数个数,只有一个参数
List<? extends StructField> inputFieldRef = argOIs.getAllStructFieldRefs();
if (inputFieldRef.size() != 1)
    throw new UDFArgumentException("需要一个参数");

// 2. 判断参数类型,参数类型为string
ObjectInspector objectInspector = inputFieldRef.get(0).getFieldObjectInspector();
if (objectInspector.getCategory() != ObjectInspector.Category.PRIMITIVE // 参数是Hive原始类型
        || !PrimitiveObjectInspector.PrimitiveCategory.STRING.equals(((PrimitiveObjectInspector)objectInspector).getPrimitiveCategory())) // 参数是Hive的string类型
    throw new UDFArgumentException("函数第一个参数为字符串"); // 当自定义UDF参数与预期不符时,抛出异常

其三:确定函数返回值类型

UDTF函数可以对于一行输入,可以产生多行输出,并且每行结果可以有多列。 自定义UDTF 的返回值类型会稍微复杂些,需要明确输出结果的所有列名和列类型

initialize() 方法的返回值类型为 StructObjectInspector

StructObjectInspector 表示了一行记录的结构,可以包括多个列。每个列有列名列类型列注释(可选)

可以通过 ObjectInspectorFactory 来获取 StructObjectInspector 实例:

/**
 * structFieldNames:列
 */
ObjectInspectorFactory.getStandardStructObjectInspector(
  List<String> structFieldNames,
  List<ObjectInspector> structFieldObjectInspectors)

structFieldNames的第n个元素,代表了第n列的名称;structFieldObjectInspectors的第n个元素,代表了第n列的类型。

structFieldNames 和 structFieldObjectInspectors 应该保持长度一致

// 只有一列,列的类型为Map<String, int>
return ObjectInspectorFactory.getStandardStructObjectInspector(
        Collections.singletonList("result_column_name"),
        Collections.singletonList(
                ObjectInspectorFactory.getStandardMapObjectInspector(
                        PrimitiveObjectInspectorFactory.javaStringObjectInspector, // Key 是 String
                        PrimitiveObjectInspectorFactory.javaIntObjectInspector // Value 是 int
                )
        )
);

process()

核心方法,自定义UDTF 的实现逻辑

代码实现步骤可以分为三部分:

  1. 参数接收
  2. 自定义UDTF核心逻辑
  3. 输出结果
/**
 * Give a set of arguments for the UDTF to process.
 *
 * @param args
 *          object array of arguments
 */
public abstract void process(Object[] args) throws HiveException;

第一步:参数接收

args 即是 自定义UDTF 的参数,传入的参数不同,会是不同的Java类型,以下是Hive常用参数类型对应的Java类型

Hive类型Java类型
tinyintByteWritable
smallintShortWritable
intIntWritable
bigintLongWritable
stringText
booleanBooleanWritable
floatFloatWritable
doubleDoubleWritable
ArrayArrayList
Map<K, V>HashMap<K, V>

参数接收示例:

// 参数null值的特殊处理
if (args[0] == null)
    return;

// 接收参数
String str = ((Text) args[0]).toString();

第二步:自定义UDTF核心逻辑

获取参数之后,到这里就是自由发挥了~

第三步:输出结果

process() 方法本身没有返回值,通过 GenericUDTF 中的 forward() 输出一行结果。forward() 可以反复调用,可以输出任意行结果

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

forward() 可以接收 ListJava数组,第n个元素代表第n列的值

List<Object> list = new LinkedList<>();
// 第一列是int
list.add(1);
// 第二列是string
list.add("hello");
// 第三列是boolean
list.add(true);

// 输出一行结果
forward(list);

close()

没有其他输入行时,调用该函数

可以进行一些资源关闭处理等最终处理