hive一共有以下几种类型的自定义函数:
分类名称 说明 内置函数举例
UDF 用户自定义函数 UDFOPPlus(与“+”对应)
UDFLike(与“LIKE”对应)
Generic UDF 通用用户自定义函数 GenericUDFBetween("Between")
UDAF 用户自定义聚合函数 无内置函数,是Generic UDAF的简化版
Generic UDAF 通用用户自定义聚合函数 SUM、COUNT、AVG
Generic UDTF 用户自定义表函数 GenericUDTFExplode("Explode")
其中通用和不通用的区别只在于通用函数可以处理不确定的数据类型(泛型)。
UDF的计算结果是把表的每一行数据进行转换,生成新的一行数据,以1:1的比例输出。
UDAF针对若干行数据进行聚合计算,得到一行输出,以N:1的比例输出。
UDTF是根据一行输入数据,对该行数据进行分解得到多行,以1:N的比例输出。
下面说一下如何编写用户自定义函数:
UDF
对于一个普通UDF来说,主要有两点需要注意的。
1、类要继承自UDF。
2、类中要重写evalute方法。
下面给出一个例子,将查询出的字段自动加上“hello world”
package UDF;
import org.apache.hadoop.hive.ql.exec.UDF;
public class helloUDF extends UDF {
public String evaluate(String str) {
try {
return "HelloWorld " + str;
} catch (Exception e) {
return null;
}
}
}
再看另外一个复杂点例子,将查询出的字段中随机挑选一位与行号拼接,如查询出的是abcdef 输出d_1(d是abcdef中随机选择的,1是行号)
package UDF;
import java.util.Random;
import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.hive.ql.udf.UDFType;
@UDFType(deterministic=false,stateful=true)
public class randomUDF extends UDF{
private Random random=new Random();
private int lineNo=0;
public String evaluate(String str){
int n=(int)random.nextDouble()*str.length();
StringBuffer sb=new StringBuffer();
sb.append(++lineNo);
sb.append("_");
sb.append(str.charAt(n));
return sb.toString();
}
public String evaluate(int a){
String str=Integer.toString(a);
int n=(int)random.nextDouble()*str.length();
StringBuffer sb=new StringBuffer();
sb.append(++lineNo);
sb.append("_");
sb.append(str.charAt(n));
return sb.toString();
}
}
在上个程序中我们对程序进行了标注@UDFType(deterministic=false,stateful=true)
其中UDFType有三个属性,分别是
1、deterministic
如果传给一个UDF的参数值相同,无论调用多少次UDF,它的返回值都是相同的,我们就认为这个UDF是确定性UDF。上例中就属于不确定的。
2、ststeful
有状态的UDF,UDF实例内部会维护一个状态,每次调用该UDF的evaluate方法,该状态可能会改变,如上例行号属性。
3、distinctLike
这个标记通常出现在聚合函数中,在做聚合时,如果碰到两个相同的值,我们只取其中一个不影响最后的聚合结果,那么distinctLike为真,否则为假。
hive会根据以上几种标记对查询进行优化,如果不填写UDFType,会影响运行效率。
通用用户自定义函数
当输入参数的数量和类型我们无法预知,则需要编写通用型的用户自定义函数。
如public String printf(String format,arg1,arg2,arg3...)
编写GenericUDF步骤:
编写通用UDF需要用到ObjectInspector
ObjectInspector中包括了数据类型,和对应该类型的取值方法。
定义大体如下:
public interface ObjectInspector
{
public static enum Category {
PRIMITIVE, LIST, MAP, STRUCT, UNION
};
String getTypeName();
Category getCategory();
String get(Object o)
}
1.继承GenericUDF类,实现以下3个方法:
public ObjectInspector initialize(ObjectInspector[] arguments)
public Object evaluate(DeferredObject[] arguments)
public String getDisplayString(String[] children)
initialize方法在SQL调用UDF函数中,首先被调用,它完成下面4件事:
(1)验证输入的类型是否预期输入
(2)设置返回值,设置返回一个与预期输出类型相符的ObjectInspector ,假设需要返回的是一个string类型,由于string类型对应的分布式类型是TextWritable,所以在initialize返回的类型是
return PrimitiveObjectInspectorFactory.writableStringObjectInspector;
(3)存储在全局变量的ObjectInspectors元素的输入
(4)设置存储变量的输出
其中第三步不是必须的,因为全局变量能在evaluate方法中以局部变量的形式声明并处理,但是在initialize存储全局变量,只需要初始化一次。
evaluate方法:处理函数从输入到输出的逻辑,返回函数处理预期结果的值
(1)第一步:将输入值,声明局部变量通过initialize方法判断合法的变量存储。
(2)第二步:处理函数逻辑,将输入通过代码逻辑得到结果并返回。
getDisplayString方法
(1)用于explain sql的时候,方便查看其返回的格式
(2)类似于java的toString方法getDisplayString方法
简而言之,UDF会先调用initialize将对应数据的取值器传入。
再调用evaluate方法将数据传入对应数据,经处理后返回结果。
下面是一个例子:
输入一个用户编号user_id,一个游戏列表game_list,输出是用户与随机游戏列表中的一款游戏,以逗号分隔:user_id,game,在hive外层使用split函数将其划分,得到用户随机的一款游戏。
源码分析:
package genericUDF;
import java.util.List;
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;
import org.apache.hadoop.hive.serde2.objectinspector.StandardListObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.io.Text;
/**
* 随机用户匹配游戏列表中的一款游戏
*
* @author huzhirong
*
*/
public class UDFRandomUidGame extends GenericUDF {
//声明全局输入变量
StandardListObjectInspector dimGameListOI;
ObjectInspector uidOI;
//声明输出变量
private final Text text = new Text();
//处理业务逻辑
@Override
public Object evaluate(DeferredObject[] vals) throws HiveException {
text.clear();
Text uid = (Text) vals[0].get();
Text seq = new Text(",");
@SuppressWarnings("unchecked")
List<Text> list = (List<Text>) dimGameListOI.getList(vals[1].get());
Text game = list.get((int) (Math.random() * list.size()));
text.append(game.getBytes(), 0, game.getLength());
text.append(seq.getBytes(), 0, seq.getLength());
text.append(uid.getBytes(), 0, uid.getLength());
return text;
}
//类似于java的toString方法,hive中在使用explain的时候调用该方法
@Override
public String getDisplayString(String[] args) {
StringBuilder sb = new StringBuilder();
if (args != null && args.length > 0) {
for (String arg : args) {
sb.append(arg).append(',');
}
}
if (sb.length() > 0) {
sb.setCharAt(sb.length() - 1, ')');
} else {
sb.append(')');
}
sb.insert(0, "random_list_element(");
return sb.toString();
}
@Override
public ObjectInspector initialize(ObjectInspector[] ois) throws UDFArgumentException {
uidOI = (ObjectInspector)ois[0];
dimGameListOI = (StandardListObjectInspector)ois[1];
//返回值类型
return PrimitiveObjectInspectorFactory.writableStringObjectInspector;
}
}
用户自定义聚合函数
聚合函数其实就是对应到了MapReduce模型shuffle过程
在Shark查询时,会有很多SparkTask并行计算,每个SparkTask将拥有一个UDAF实例。结合上面的分析,我们把上述过程分解成UDAF的四个回调函数
iterate :每条记录都会被作为参数传给该函数
merge:对于GroupBy键值相同的记录,归并调用
terminatePartial:如果是部分数据先聚合的,那么部分聚合完成后会调用这个函数。
terminate:在SparkTask结束前会调用该函数,输出最终结果。
下面是一个求平均值的聚集函数
public final class UDAFAvg extends UDAF{
public static class UDAFAvgState{
private long count;
private double sum;
}
public static class UDAFAvgEvaluator implements UDAFEvaluator{
UDAFAvgState state;
public UDAFAvgEvaluator(){
super();
state=new UDAFAvgState();
init();
}
public void init(){
state.sum=0;
state.count=0;
}
public void iterate(Double o){
if(o !=null){
state.sum+=o;
state.count++;
}
}
public UDAFAvgState terminatePartial(){
return state.count==0?null:state;
}
public void merge(UDAFAvgState o){
if(o!=null){
state.sum+=o.sum;
state.count+=o.count;
}
}
public Double terminate(){
return state.count==0?null:Double.valueOf(state.sum/state.count);
}
}
}