表生产函数
一行输入,输出多行结果
实现 自定义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 初始化时被调用一次,执行一些初始化操作,包括:
判断函数参数个数
判断函数参数类型
确定函数返回值类型
除此之外,用户在这里还可以做一些自定义的初始化操作,比如初始化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 structFieldNames,
List structFieldObjectInspectors)
复制代码
structFieldNames的第n个元素,代表了第n列的名称;structFieldObjectInspectors的第n个元素,代表了第n列的类型。
structFieldNames 和 structFieldObjectInspectors 应该保持长度一致
// 只有一列,列的类型为Map
return ObjectInspectorFactory.getStandardStructObjectInspector(
Collections.singletonList("result_column_name"),
Collections.singletonList(
ObjectInspectorFactory.getStandardMapObjectInspector(
PrimitiveObjectInspectorFactory.javaStringObjectInspector, // Key 是 String
PrimitiveObjectInspectorFactory.javaIntObjectInspector // Value 是 int
)
)
);
复制代码
process()
核心方法,自定义UDTF 的实现逻辑
代码实现步骤可以分为三部分:
参数接收
自定义UDTF核心逻辑
输出结果
/**
* 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
MapHashMap
参数接收示例:
// 参数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() 可以接收 List 或 Java数组,第n个元素代表第n列的值
List list = new LinkedList<>();
// 第一列是int
list.add(1);
// 第二列是string
list.add("hello");
// 第三列是boolean
list.add(true);
// 输出一行结果
forward(list);
复制代码
close()
没有其他输入行时,调用该函数
可以进行一些资源关闭处理等最终处理