Hive自定義表生成函數UDTF的自定義實現Demo

前言

  • Hive:2.3.0

  • 由於實際生產環境中,Hive自帶的內建函數無法覆蓋所有的應用場景,所以時常需要進行自定義函數User-Defined Function(UDF),以滿足實際生產需求。

  • 本文主要演示如何實現自定義表生成函數User-Defined Table-Generating Function(UDTF),此類函數的特點是一進多出

  • 創建Hive函數時,如果指定爲臨時的(temporary)則可以在所有數據庫下使用,但只能在當前會話中使用,退出後自動刪除;如果指定爲持久的(permanent)則只能在指定數據庫(默認當前數據庫)下使用,但可以在其他會話中使用,退出後依舊保留

  • 官方參考文檔1官方參考文檔2

  • 溫馨提示:Hive自定義組件打包成jar包時,不要同時打包依賴,避免各種版本衝突,只將額外的依賴添加到classpath中即可


自定義UDTF函數的實現步驟

1)繼承org.apache.hadoop.hive.ql.udf.generic.GenericUDTF

2)實現initializeprocessclose方法

3)打包成jar文件

4)在Hive CLI中將jar包添加到classpath

add jar <path_to_jar>

5)創建對應函數

create function <db_name>.<function_name> AS '<full_class_name>'

自定義UDTF實現Demo

自定義實現了UDTF函數split_explode,此函數的功能是,將傳入的字符串按照指定的正則表達式拆分成字符串數組,然後形成多個列,類似於split函數和explode函數的功能組合,源碼如下:

package com.tomandersen.hive.udtfs;

import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTFExplode;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTFJSONTuple;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTFPosExplode;
import org.apache.hadoop.hive.serde.serdeConstants;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.StructField;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.StringObjectInspector;
import org.apache.hadoop.io.Text;

import java.util.ArrayList;
import java.util.List;

/**
 * <h3>Hive自定義表生成函數UDTF的實現Demo</h3>
 * 功能: 將字符串按照指定分隔符分割成獨立的單詞,並返回多行.
 * <p>
 * UDTF輸入: 待分割字符串,分隔符正則表達式
 * UDTF輸出: 分割後產生的單詞列
 * <p>
 * 參考: {@link GenericUDTFJSONTuple},{@link GenericUDTFExplode},{@link GenericUDTFPosExplode}
 *
 * @author TomAndersen
 * @version 1.0
 * @date 2020/5/21
 */

// 用於生成Function document.可以使用desc function <function_name>命令查看函數使用文檔.
@Description(
        name = "split_explode",
        value = "_FUNC_(str,regex) - Splits str around occurrences that match regex and " +
                "separates the elements into multiple rows."
)
public class CustomUDTFSplitAndExplode extends GenericUDTF {

    // 保存所有輸入字段對應的 ObjectInspector
    private transient ObjectInspector[] inputFieldIOs;
    // 保存輸出列個數
    private int numCols;
    // 保存輸出列
    private transient Text[] retCols;
    // 保存空輸出列,用於在特殊情況輸出
    private transient Object[] nullCols;
    // 保存輸入參數中的正則表達式
    private String regex;
    // 保存正則表達式regex是否已經獲取
    private boolean regexParsed = false;


    /**
     * 此方法一般用於檢查輸入參數是否合法,每個實例只會調用一次.
     * 一般用於用於檢查UDTF函數的輸入參數,以及生成一個用於查看新生成字段數據的StructObjectInspector.
     *
     * @param argOIs {@link StructObjectInspector},傳入的是所有輸入參數字段的查看器
     * @return {@link StructObjectInspector},返回的是新生成字段的查看器
     * @date 2020/5/22
     */
    @Override
    public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
        // 1.輸入參數檢查
        List<? extends StructField> inputFields = argOIs.getAllStructFieldRefs();// 獲取所有的輸入參數字段
        int argsLength = inputFields.size();

        inputFieldIOs = new ObjectInspector[argsLength];// 獲取所有輸入參數字段對應的檢查器(Inspector)
        for (int i = 0; i < argsLength; i++) {
            inputFieldIOs[i] = inputFields.get(i).getFieldObjectInspector();
        }

        if (argsLength != 2) {// 檢查參數個數
            throw new UDFArgumentException("split_explode(str,regex) takes only two arguments: " +
                    "the string and regular expression");
        }

        for (int i = 0; i < argsLength; ++i) {// 檢查參數類型
            if (inputFieldIOs[i].getCategory() != ObjectInspector.Category.PRIMITIVE ||
                    !inputFieldIOs[i].getTypeName().equals(serdeConstants.STRING_TYPE_NAME)) {
                throw new UDFArgumentException("split_explode(str,regex)'s arguments have to be string type");
            }
        }

        // 2.成員變量賦值
        numCols = 1;
        retCols = new Text[numCols];
        nullCols = new Object[numCols];
        for (int i = 0; i < numCols; i++) {
            retCols[i] = new Text();
            nullCols[i] = null;
        }

        // 3.定義輸出字段的列名(字段名)
        // 此處設置的各個列名很可能會在用戶執行Hive SQL查詢時,會被其指定的列別名所覆蓋,如果查詢時未指定別名,則顯示此列名.
        // 此自定義表生成函數UDTF只輸出單列,所以只添加了單個列的列名
        ArrayList<String> fieldNames = new ArrayList<>();
        fieldNames.add("split_explode_col");

        // 4.定義輸出字段對應字段類型的ObjectInspector(對象檢查器),一般用於查看/轉換/操控該字段的內存對象
        // 只是查看器,並非是真正的字段對象
        ArrayList<ObjectInspector> fieldOIs = new ArrayList<>();
        // 由於輸出字段的數據類型都是String類型
        // 因此使用檢查器PrimitiveObjectInspectorFactory.javaStringObjectInspector
        fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);

        // 5.生成並返回輸出對象的對象檢查器
        return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
    }

    // 此方法爲核心處理方法,聲明主要的處理過程,如果是對錶使用函數,則會對錶中每一行數據通過同一個實例調用一次此方法.
    // 輸入參數爲待處理的字符串以及分隔符正則表達式,每次處理完後直接使用forward方法將數據傳給收集器.
    @Override
    public void process(Object[] args) throws HiveException {
        // 1.通過 ObjectInspector 獲取待處理字符串
        String str = ((StringObjectInspector) inputFieldIOs[0]).getPrimitiveJavaObject(args[0]);
        if (str == null || str.length() == 0) {// 如果對象爲null則直接輸出空行
            forward(nullCols);
            return;
        }
        // 2.獲取分割符正則表達式
        if (!regexParsed) {
            regex = ((StringObjectInspector) inputFieldIOs[1]).getPrimitiveJavaObject(args[1]);
            regexParsed = true;
        }
        // 3.按照正則表達式分割字符串
        String[] words = str.split(regex);
        // 4.輸出數據
        // 將輸出行通過forward方法傳給收集器,每調用一次forward則代表在新生成字段中生成一行數據
        for (String word : words) {
            // 將輸出數據放入輸出數組的指定位置(每次寫入時爲覆蓋寫入),然後輸出.本UDTF固定輸出單列.
            retCols[0].set(word);
            forward(retCols);
        }
    }

    // 釋放相關資源,一般不需要
    @Override
    public void close() throws HiveException {

    }

    // 返回此UDTF對應的函數名
    @Override
    public String toString() {
        return "split_explode";
    }
}


End~

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