一、Hive自定义函数介绍
1.内置函数
Hive 自带了一些函数。比如:max/min等,但是数量有限,自己可以通过自定义UDF来方便的扩展。
2.自定义函数
当Hive提供的内置函数无法满足你的业务处理需要时,此时就可以考虑使用用户自定义函数(UDF:user-defined function)。
3.用户自定义函数类别
用户自定义函数根据输入参数和输出结果的个数分为以下三类:
① UDF(User-Defined-Function) 一进一出
② UDAF(User-Defined Aggregation Function) 聚合函数,多进一出,如:count/max/min
③ UDTF(User-Defined Table-Generating Functions) 炸裂函数,一进多出,如:explode()
4.官方文档地址
5.编程实现步骤
① 继承Hive提供的类:
org.apache.hadoop.hive.ql.udf.generic.GenericUDF
·org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
② 实现类中的抽象方法
③ 在hive的命令行窗口创建函数
二、具体实现
为了能够更好地演示过程,本文以编写一个判断字符串长度的函数为例,函数命名为mystrlen(),具体实现效果如下图所示。
1.准备工作
① 创建一个Maven工程Hive
② 在工程项目的pom.xml文件中导入依赖,具体代码如下。本文选用的是Hive3.1.2版本,大家可以根据自己的版本自行更改。
<dependencies>
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>3.1.2</version>
</dependency>
</dependencies>
2.继承Hive提供的类
经过分析不难发现,该函数应该以一个字符串为输入参数,最终输出一个int型的整数以表明该字符串的长度。其对应的自定义函数类别是UDF(一进一出)类型。所以我们应该继承前文所提到的GenericUDF类。
创建my_strlen类以继承GenericUDF类。由于GenericUDF类为抽象类,所以在继承后,我们必须实现其抽象方法,如下图所示。我们的主要任务便是对前两个函数进行重写。
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
/**
* 自定义UDF函数,需要继承GenericUDF类
* 需求: 计算指定字符串的长度
*/
public class my_strlen extends GenericUDF {
/**
*
* @param arguments 输入参数类型的鉴别器对象
* @return 返回值类型的鉴别器对象
* @throws UDFArgumentException
*/
@Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
return null;
}
/**
* 函数的逻辑处理
* @param arguments 输入的参数
* @return 返回值
* @throws HiveException
*/
@Override
public Object evaluate(DeferredObject[] arguments) throws HiveException {
return null;
}
@Override
public String getDisplayString(String[] children) {
return null;
}
}
看到上述idea自动生成的代码,可能大家有点慌了。ObjectInspector是什么?DeferredObject又是什么?这些函数具体是做啥的?让我们一步一步的来。
在这里,第一个方法initialize()的作用为对自定义函数进行初始化。具体需要我们实现的有:检验函数的参数个数和参数类型是否正确,并且返回函数返回值类型的ObjectInspector。对于第二个方法evaluate,我们需要实现函数的具体功能,并且返回函数最终的输出结果。两个方法的形参都是argument,顾名思义,此形参传递的便是函数的参数信息。
对于不认识的类,我们首先应该去翻译其类名,然后分析其源码。ObjectInspector,顾名思义,该类应该是一个鉴别器,具体鉴别什么就需要我们对源码进行分析。让我们看下列源码,不难发现一些关键词 :gettype、getCategory,那么这便是一个对象数据类型的鉴别器。对于第一个方法initialize()的形参,便是由若干个函数输入参数的类型鉴别器所组成的数组。
public interface ObjectInspector extends Cloneable {
String getTypeName();
Category getCategory();
public static enum Category {
PRIMITIVE,
LIST,
MAP,
STRUCT,
UNION;
private Category() {
}
}
}
再来观察DeferredObject的源码。上文提到我们需要在evaluate()方法中实现函数的具体功能,那么首先我们需得到函数的参数值,再根据参数来编写功能逻辑。在DeferredObject的源码中我们发现了get()方法,根据经验我们可以通过此方法来获得函数的参数值。
public static interface DeferredObject {
void prepare(int version) throws HiveException;
Object get() throws HiveException;
};
3.实现类中的抽象方法
分析完各个模块,就可以开始重写方法啦。initialize()方法重写如下,其难点在于对函数输入参数类型的判断和如何返回int类型的鉴别器对象。
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
// 判断输入参数的个数
if(arguments.length !=1){
throw new UDFArgumentLengthException("Input Args Length Error!!!");
}
// 判断输入参数的类型
if(!arguments[0].getCategory().equals(ObjectInspector.Category.PRIMITIVE)){
throw new UDFArgumentTypeException(0,"Input Args Type Error!!!");
}
//函数本身返回值为int,需要返回int类型的鉴别器对象
return PrimitiveObjectInspectorFactory.javaIntObjectInspector;
}
对于输入参数类型的判断,仔细观察上文中的ObjectInspector源码,发现有一个静态枚举类Category,也正是getCategory()方法的返回值类型。所以该枚举类的作用是用来限制ObjectInspector类型鉴别器所鉴别类型的结果,我们可以使用getCategory()方法来获取函数参数的类型并且直接调用静态枚举类的成员,比较二者是否相同来判断函数参数类型是否正确(arguments[0].getCategory().equals(ObjectInspector.Category.PRIMITIVE))。在这里我使用ObjectInspector.Category.PRIMITIVE来调用枚举类Category中的PRIMITIVE,因为我们需要限制输入参数的类型为String,其对应的便是PRIMITIVE(原始类型)。
如果在前两步的判断中发现参数不符合要求,我们便可以抛出异常,代码中的两个异常是initialize方法所抛出异常UDFArgumentException的子类,适用于不同的场景,可以更好地起到异常提示效果。
对于返回int类型的鉴别器对象,因ObjectInspector是一个接口,所以我们需要调用工厂类中的实例,通过PrimitiveObjectInspectorFactory.javaIntObjectInspector获得。关于何为静态工厂类中的实例大家可以参考以下两篇文章:java静态工厂方法详细解析;Hive之ObjectInspector接口解析笔记
对于evaluate()方法的实现,因函数本身逻辑简单,所以其实现难度不大。需要注意的是在使用get()方法获取到参数值后需要toString(),因为从DeferredObject源码中发现get()方法的返回值类型是object,所以需要我们将其转化为String类型。
public Object evaluate(DeferredObject[] arguments) throws HiveException {
return arguments[0].get().toString().length();
}
4.在hive的命令行窗口创建函数
首先将代码打成jar包上传到服务器的任意位置,然后在hive命令行使用如下命令将jar包添加到hive的classpath中。
创建临时函数与开发好的java class关联 ,其中temporary代表创建临时函数,若创建永久函数需省略;mystrlen是我们的函数名;最后需要跟全类名。
运行效果如下:
三、总结
Hive创建自定义函数的逻辑并不难,只需继承相关类,实现相关方法,打成jar包上传集群即可。但在代码编写阶段有一定难度,需要一定的java基础。
标签:函数,自定义,ObjectInspector,hive,Hive,类型,源代码 From: https://blog.csdn.net/ffads/article/details/140451546