首先什么是UDF,UDF的全称为user-defined function,用户定义函数,为什么有它的存在呢?有时,你要写的查询无法轻松地使用Hive提供的内置函数来表示,通过写UDF,Hive就可以方便地插入用户写的处理代码并在查询中使用它们,相当于在HQL(Hive SQL)中自定义一些函数。

首先UDF必须用java语言编写,Hive本身就是用java写的。所以想学好hadoop这个分布式框架的相关技术,熟练使用java就是基本功了!

Hive中有三种UDF:(普通)UDF、用户定义聚集函数(user-defined aggregate function,UDAF)、用户定义表生成函数(user-defined table-generating function,UDTF)。

UDF操作作用于单个数据行,并且产生一个数据行作为输出。大多数函数都属于这一类(比如数学函数和字符串函数)。

UDAF 接受多个输入数据行,并产生一个输出数据行。想COUNT和MAX这样的函数就是聚集函数。

UDTF 操作作用于单个数据行,并且产生多个数据行-------一个表作为输出

首先一个UDF必须满足下面两个条件:

1 一个UDF必须是org.apache.hadoop.hive.ql.exec.UDF的子类(换句话说就是我们一般都是去继承这个类)

2 一个UDF必须至少实现了evaluate()方法

注意UDF名不是大小写敏感的

一个UDAF计算函数必须实现下面的5个方法:

1 init()方法  init()方法负责初始化计算函数并重设它的内部状态。在MaximumIntUDAFEvaluator中,我们把存放最终结果的IntWritable对象设置为null。我们使用null来表示目前还没有对任何值进行聚集计算,这和对空集NULL计算最大值应有的结果是一致的。

2 iterate()方法 每次对一个新值进行聚集计算时都会调用iterate()方法。计算函数要根据聚集计算的结果更新其内部状态。iterate()接受的参数和Hive中被调用函数的参数使对应的。

3 terminatePartial()方法  Hive需要部分聚集结果时会调用terminatePartial()方法。这个方法必须返回一个封装了聚集计算当前状态的对象。

4 merge()方法  在Hive决定要合并一个部分聚集值和另一个部分聚集值时会调用merge()方法。该方法接受一个对象作为输入。这个对象的类型必须和terminatePartial()方法返回的类型一致。

5 terminate() Hive需要最终聚集结果时会调用terminate()方法。计算函数需要把状态作为一个值返回。

具体去写UDF的过程我这里就不具体说了,我来说说,怎样去把我们写好的UDF放到Hive中去使用:

首先我们将用java写好的UDF函数编译后的Java类打包成为一个JAR文件,并在Hive中注册这个文件(相当于告诉Hive这个是我写的UDF):
ADD JAR /path/hive-sample.jar;
给我们写的UDF中的Strip类起个别名
CREATE TEMPORARY FUNCTION strip AS 'com.hadoop.hive.Strip';

经过了上面这个过程就可以在Hive中使用这个UDF了 

比如 SELECT strip(‘bee‘)  FROM dummy;
 

Hive有两个不同的接口编写UDF程序。一个是基础的UDF接口,一个是复杂的GenericUDF接口。

hive udf实现流程 hive udf原理_Hive


org.apache.hadoop.hive.ql.exec.UDF:

基础UDF的函数读取和返回基本类型,即Hadoop和Hive的基本类型。如,Text、IntWritable、LongWritable、DoubleWritable等。

org.apache.hadoop.hive.ql.udf.generic.GenericUDF:

复杂的GenericUDF可以处理Map、List、Set类型。

@Describtion注解是可选的,用于对函数进行说明,其中的_FUNC_字符串表示函数名,当使用DESCRIBE FUNCTION命令时,替换成函数名。@Describtion包含三个属性:
name:用于指定Hive中的函数名。
value:用于描述函数的参数。
extended:额外的说明,如,给出示例。当使用DESCRIBE FUNCTION EXTENDED name的时候打印。
而且,Hive要使用UDF,需要把Java文件编译、打包成jar文件,然后将jar文件加入到CLASSPATH中,最后使用CREATE FUNCTION语句定义这个Java类的函数:
hive> ADD jar /root/experiment/hive/hive-0.0.1-SNAPSHOT.jar;
hive> CREATE TEMPORARY FUNCTION hello AS "edu.wzm.hive.HelloUDF";
hive> DROP TEMPORARY FUNCTION IF EXIST hello;

简单UDF的实现很简单,只需要继承UDF,然后实现evaluate()方法就行了。

@Description(
     name = "hello",
     value = "_FUNC_(str) - from the input string"
         + "returns the value that is \"Hello $str\" ",
     extended = "Example:\n"
         + " > SELECT _FUNC_(str) FROM src;"
 )
 public class HelloUDF extends UDF{
     
     public String evaluate(String str){
         try {
             return "Hello " + str;
         } catch (Exception e) {
             // TODO: handle exception
             e.printStackTrace();
             return "ERROR";
         }
     }
 }

把jar文件添加后,创建函数hello,然后执行结果如下:
hive (mydb)> SELECT hello(name) FROM employee;
OK
Hello John Doe
Hello Mary Smith
Hello Todd Jones
Hello Bill King
Hello Boss Man
Hello Fred Finance
Hello Stacy Accountant
Time taken: 0.198 seconds, Fetched: 7 row(s)

3.GenericUDF
GenericUDF实现比较复杂,需要先继承GenericUDF。这个API需要操作Object Inspectors,并且要对接收的参数类型和数量进行检查。GenericUDF需要实现以下三个方法:

//这个方法只调用一次,并且在evaluate()方法之前调用。该方法接受的参数是一个ObjectInspectors数组。该方法检查接受正确的参数类型和参数个数。
 abstract ObjectInspector initialize(ObjectInspector[] arguments);
  
 //这个方法类似UDF的evaluate()方法。它处理真实的参数,并返回最终结果。
 abstract Object evaluate(GenericUDF.DeferredObject[] arguments);
  
 //这个方法用于当实现的GenericUDF出错的时候,打印出提示信息。而提示信息就是你实现该方法最后返回的字符串。
 abstract String getDisplayString(String[] children);下面是实现GenericUDF,判断一个数组或列表中是否包含某个元素的例子:
class ComplexUDFExample extends GenericUDF {
  
   ListObjectInspector listOI;
   StringObjectInspector elementsOI;
   StringObjectInspector argOI;
  
   @Override
   public String getDisplayString(String[] arg0) {
     return "arrayContainsExample()"; // this should probably be better
   }
  
   @Override
   public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
     if (arguments.length != 2) {
       throw new UDFArgumentLengthException("arrayContainsExample only takes 2 arguments: List<T>, T");
     }
     // 1. Check we received the right object types.
     ObjectInspector a = arguments[0];
     ObjectInspector b = arguments[1];
     if (!(a instanceof ListObjectInspector) || !(b instanceof StringObjectInspector)) {
       throw new UDFArgumentException("first argument must be a list / array, second argument must be a string");
     }
     this.listOI = (ListObjectInspector) a;
     this.elementsOI = (StringObjectInspector) this.listOI.getListElementObjectInspector();
     this.argOI = (StringObjectInspector) b;
     
     // 2. Check that the list contains strings
     if(!(listOI.getListElementObjectInspector() instanceof StringObjectInspector)) {
       throw new UDFArgumentException("first argument must be a list of strings");
     }
     
     // the return type of our function is a boolean, so we provide the correct object inspector
     return PrimitiveObjectInspectorFactory.javaBooleanObjectInspector;
   }
   
   @Override
   public Object evaluate(DeferredObject[] arguments) throws HiveException {
     
     // get the list and string from the deferred objects using the object inspectors
 //    List<String> list = (List<String>) this.listOI.getList(arguments[0].get());
     int elemNum = this.listOI.getListLength(arguments[0].get());
 //    LazyListObjectInspector llst = (LazyListObjectInspector) arguments[0].get();
 //    List<String> lst = llst.
     
     LazyString larg = (LazyString) arguments[1].get();
     String arg = argOI.getPrimitiveJavaObject(larg);
     
 //    System.out.println("Length: =======================================================>>>" + elemNum);
 //    System.out.println("arg: =======================================================>>>" + arg);
     // see if our list contains the value we need
     for(int i = 0; i < elemNum; i++) {
         LazyString lelement = (LazyString) this.listOI.getListElement(arguments[0].get(), i);
         String element = elementsOI.getPrimitiveJavaObject(lelement);
         if(arg.equals(element)){
             return new Boolean(true);
         }
     }
     return new Boolean(false);
   }
   
 }

注意:在Hive-1.0.1估计之后的版本也是,evaluate()方法中从Object Inspectors取出的值,需要先保存为Lazy包中的数据类型(org.apache.hadoop.hive.serde2.lazy),然后才能转换成Java的数据类型进行处理。否则会报错,解决方案可以参考Hive报错集锦中的第5个。

把jar文件添加后,创建函数contains,然后执行结果如下:
hive (mydb)> select contains(subordinates, subordinates[0]), subordinates from employee;
OK
true    ["Mary Smith","Todd Jones"]
true    ["Bill King"]
false    []
false    []
true    ["John Doe","Fred Finance"]
true    ["Stacy Accountant"]
false    []
Time taken: 0.169 seconds, Fetched: 7 row(s)

现在我们在回头看看GenericUDF的模型:

这个UDF使用默认的构造方法初始化。
initialize()和一个Object Inspectors数组(ListObjectInspector、StringObjectInspector)参数一起被调用。
先检查参数个数(2个),和这些参数的类型;
为evaluate()方法保存Object Inspectors(listOI、argOI、elementsOI)
返回一个ObjectInspector(BooleanObjectInspector),且是Hive可以读取的方法结果。 
对于查询的每一行都调用evaluate()(如,contains(subordinates, subordinates[0]))
取出存储在Object Inspectors中的值;
处理完initialize()方法返回的Object Inspectors之后,返回一个值(如,list.contains(elemement) ? true : false)。