Hive的UDF編程-GenericUDF編程

UDF簡介

在Hive中,用戶可以自定義一些函數,用於擴展HiveQL的功能,而這類函數叫做UDF(用戶自定義函數)。UDF分爲兩大類:UDAF(用戶自定義聚合函數)和UDTF(用戶自定義表生成函數)。在介紹UDAF和UDTF實現之前,我們先在本章介紹簡單點的UDF實現——UDF和GenericUDF,然後以此爲基礎在下一章介紹UDAF和UDTF的實現。

Hive有兩個不同的接口編寫UDF程序。

一個是基礎的UDF接口,一個是複雜的GenericUDF接口。
UDF 基礎UDF的函數讀取和返回基本類型,即Hadoop和Hive的基本類型。如,Text、IntWritable、LongWritable、DoubleWritable等。
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中有詳細的介紹

這次我們重點介紹GenericUDF,繼承這個類需要實現三個方法

//這個方法只調用一次,並且在evaluate()方法之前調用。該方法接受的參數是一個ObjectInspectors數組。該方法檢查接受正確的參數類型和參數個數。  
abstract ObjectInspector initialize(ObjectInspector[] arguments);  
  
//這個方法類似UDF的evaluate()方法。它處理真實的參數,並返回最終結果。  
abstract Object evaluate(GenericUDF.DeferredObject[] arguments);  
  
//這個方法用於當實現的GenericUDF出錯的時候,打印出提示信息。而提示信息就是你實現該方法最後返回的字符串。  
abstract String getDisplayString(String[] children);  

需求

這裏我們設置一個需求是這樣的,在一個sql中查找某列數組是否包含另外一個值。下面這個例子中就是需要實現hello這個函數

//舉一個簡單的seq例子,,因爲在切割後的數組中會包含aaa,所以我們希望的返回結果是true,
select hello(split('aaa,bbb',','),'aaa');

下面是我們的GenericUDF函數的代碼

/**
 * Copyright (C), 2015-2019, XXX有限公司
 * FileName: GenericUDFArrayTest
 * Author:   72038714
 * Date:     2019/7/24 11:45
 * Description: xxx
 * History:
 * <author>          <time>          <version>          <desc>
 * shipengfei                    版本號              描述
 */
package udf.generic;

import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorUtils;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.io.BooleanWritable;


/**
 * 〈一句話功能簡述〉<br> 
 * 〈xxx〉
 *
 * @author 72038714
 * @create 2019/7/24
 * @since 1.0.0
 */
public class GenericUDFArrayTest extends GenericUDF {

    private transient ObjectInspector value0I;
    private transient ListObjectInspector arrayOI;
    private transient ObjectInspector arrayElementOI;
    private BooleanWritable result;


    public ObjectInspector initialize(ObjectInspector[] objectInspectors) throws UDFArgumentException {

        //判斷是否輸入的參數爲2
        if (objectInspectors.length != 2){
            throw new UDFArgumentException("args must accept 2 args");
        }

        //判斷第一個參數是否是list
        if (!(objectInspectors[0].getCategory().equals(ObjectInspector.Category.LIST))){
            throw new UDFArgumentTypeException(0, "\"array\" expected at function ARRAY_CONTAINS, but \""
                    + objectInspectors[0].getTypeName() + "\" " + "is found");
        }

        //將參數賦值給私有變量
        this.arrayOI = ((ListObjectInspector) objectInspectors[0]);
        this.arrayElementOI=this.arrayOI.getListElementObjectInspector();
        this.value0I= objectInspectors[1];

        //數組元素是否與第二個參數類型相同
        if(!(ObjectInspectorUtils.compareTypes(this.arrayOI,this.value0I))) {
            throw new UDFArgumentTypeException(1,
                    "\"" + this.arrayElementOI.getTypeName() + "\"" + " expected " +
                            "at function ARRAY_CONTAINS, but "
                            + "\"" + this.value0I.getTypeName() + "\"" + " is found");
        }

        //判斷ObjectInspector是否支持第二個參數類型
        if (!(ObjectInspectorUtils.compareSupported(this.value0I))) {
                throw new UDFArgumentException("The function ARRAY_CONTAINS does not support comparison for \""
                        + this.value0I.getTypeName() + "\"" + " types");

        }

        this.result=new BooleanWritable(true);
            return PrimitiveObjectInspectorFactory.writableBooleanObjectInspector;
    }



    public Object evaluate(DeferredObject[] deferredObjects) throws HiveException {
        this.result.set(false);

        Object array= deferredObjects[0].get();

        Object value= deferredObjects[1].get();

        Integer arrayLength = this.arrayOI.getListLength(array);

        //傳入第二個參數是否爲nul,或者傳入參數長度爲0 檢驗傳入參數
        if (value == null || arrayLength<=0){
            return this.result;
        }

        //遍歷array中的類型,判斷是否與第二個參數相等
        for (int i=0;i<arrayLength;i++) {
            Object listElement = this.arrayOI.getListElement(array, i);

            //判斷包含如果本次循環的數組元數爲null,或者沒有匹配成功,跳過本次循環
            if (listElement == null || ObjectInspectorUtils.compare(value,value0I,listElement,arrayOI) != 0){
                continue;
            }
            //如果匹配成功,將result設置爲true
            result.set(true);

            break;

        }

        return result;
    }

    public String getDisplayString(String[] strings) {
        assert (strings.length == 2);
        return "array_contains(" + strings[0] + ", " + strings[1] + ")";
    }
}

創建函數

  1. 代碼編寫完成後,將代碼打包
  2. 將打包後的文件上傳到分佈式集羣
  3. 啓動hive 使用add jar命令 add jar 上傳的路徑/*.jar文件
  4. hive執行 create function hello as 'udf.generic.GenericUDFArrayTest';
  5. 執行需求提出的sql代碼,測試放回結果;

ps:爲了防止代碼書寫錯誤,可以先複製代碼創建好函數測試,再做代碼的理解

文章參考

作者:raincoffee
鏈接:https://www.jianshu.com/p/ca9dce6b5c37

有問題歡迎留言討論

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章