自定义UDAF操作

自定义类需要继承AbstractGenericUDAFResolver类,或者实现GenericUDAFResolver2的接口,AbstractGenericUDAFResolver类本身也是实现GenericUDAFResolver2的接口,主要作用是实现参数类型检查和操作符重载

1、自定义类中需要根据需求重写

public GenericUDAFEvaluator getEvaluator(TypeInfo[] parameters) throws SemanticException { return new TotalNumOfLettersEvaluator(); }

主要目的是校验UDAF的入参个数和入参类型并返回Evaluator对象。调用者传入不同的参数时,向其返回不同的Evaluator或者直接抛出异常。这个重写的方法getEvaluator在udaf中扮演了对传入参数的检查,以及返回一个新的类进行udaf操作的作用。

2、使用的参数类型ObjectInspector

使用ObjectInspector类将传入参数序列化为不同的数据格式,达到数据流转无障碍的目的。

ObjectInspector类是在Serde包下实现序列化和反序列化的辅助类。Serde一方面用在针对inputformat中RecordReader读取数据的解析和最终结果的保存,另一方面,在map和reduce之间有一层shuffle,中间结果由hadoop完成shuffle后也需要读取并反序列化成内部的object,这个object实际上通常是一个Array或者list,但hive会提供一个StandardStructObjectInspector给用户进行该Object的访问。如下

ObjectInspector oi = TypeInfoUtils.getStandardJavaObjectInspectorFromTypeInfo(parameters[0]);

3、TotalNumOfLettersEvaluator执行自定义udaf操作。

这个新的类需要继承GenericUDAFEvaluator来完成udaf操作

public static class TotalNumOfLettersEvaluator extends GenericUDAFEvaluator {}

在操作udaf时需要执行以下操作过程。

//每个子类都应该覆盖这个函数
//子类应该调用super。初始化(m,参数)以获得模式设置。
// 确定各个阶段输入输出参数的数据格式ObjectInspectors
//这是我非常想说的类,init()他会根据model值的不同来决定输入的类型和输出的类型,非常的灵活。
// 并且注意,init()的调用不是单次的,是多次的。
public ObjectInspector init(Mode m, ObjectInspector[] parameters){}
// map阶段,迭代处理输入sql传过来的列数据
// 注意这里的迭代,当map阶段从表中读取一行时,就会调用一次iterate()方法,如果存在多行,就会调用多次。
public void iterate(AggregationBuffer agg, Object[] parameters) throws HiveException {}
// map与combiner结束返回结果,得到部分数据聚集结果
public Object terminatePartial(AggregationBuffer agg) throws HiveException {}
//将terminatePartial返回的部分聚合数据进行合并,需要使用到对应的OI。
public void merge(AggregationBuffer agg, Object partial) throws HiveException {}
//结束,生成最终结果。
public Object terminate(AggregationBuffer agg) throws HiveException {}

4、定义新的类满足udaf操作需求

下面的类为求和运算

/**
 * 存储当前字符总数的类
 */
//AggregationBuffer 允许我们保存中间结果,通过定义我们的buffer,
// 我们可以处理任何格式的数据,在代码例子中字符总数保存在AggregationBuffer 。
static class LetterSumAgg implements AggregationBuffer {
    int sum = 0;
    void add(int num){
        sum += num;
    }
}

5、聚合函数UDAF-统计表字段字符个数

多案例连接:http://svn.apache.org/repos/asf/hive/branches/branch-0.8/ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDAFMax.java

package com.hello.hive;/**
 * @ProjectName: hive
 * @Package: com.hello.hive
 * @ClassName: TotalNumOfLettersGenericUDAF
 * @Author: dongsong
 * @Description: 聚合函数UDAF-统计表字段字符个数
 * @Date: 2019/8/7 10:28
 * @Version: 1.0
 */

import com.hello.model.Person;
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.parse.SemanticException;
import org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils;

import java.util.HashMap;
import java.util.Map;

/**
 *@program: hive
 *@description: 聚合函数UDAF-统计表字段字符个数
 *@author: by song
 *@create: 2019-08-07 10:28
 */

@Description(name = "letters", value = "_FUNC_(expr) - 返回该列中所有字符串的字符总数")
public class TotalNumOfLettersGenericUDAF extends AbstractGenericUDAFResolver {

    @Override
    public GenericUDAFEvaluator getEvaluator(TypeInfo[] parameters)
            throws SemanticException {
        if (parameters.length != 1) {
            throw new UDFArgumentTypeException(parameters.length - 1,
                    "Exactly one argument is expected.");
        }
        //Serde实现数据序列化和反序列化以及提供一个辅助类ObjectInspector帮助使用者访问需要序列化或者反序列化的对象。
        //
        //Serde层构建在数据存储和执行引擎之间,实现数据存储+中间数据存储和执行引擎的解耦。
        //这里为什么提到数据存储和中间数据存储两个概念,因为数据序列化和反序列化不仅仅用在对目标文件的读取和结果数据写入,
        // 还需要实现中间结果保存和传输,hive最终会将SQL转化为mapreduce程序,而mapreduce程序需要读取原始数据,并将最终的结果数据写入存储介质,
        // Serde一方面用在针对inputformat中RecordReader读取数据的解析和最终结果的保存,另一方面,在map和reduce之间有一层shuffle,
        // 中间结果由hadoop完成shuffle后也需要读取并反序列化成内部的object,这个object实际上通常是一个Array或者list,
        // 但hive会提供一个StandardStructObjectInspector给用户进行该Object的访问。

        //作用主要是解耦数据使用与数据格式,使得数据流在输入输出端切换不同的输入输出格式,不同的Operator上使用不同的格式。
        ObjectInspector oi = TypeInfoUtils.getStandardJavaObjectInspectorFromTypeInfo(parameters[0]);
        //getCategory()方法获取ObjectInspector 对象的类型
        System.out.println("getCategory()方法获取ObjectInspector 对象的类型");
        System.out.println(oi.getCategory().name());
        if (oi.getCategory() != ObjectInspector.Category.PRIMITIVE){
            throw new UDFArgumentTypeException(0,
                    "Argument must be PRIMITIVE, but "
                            + oi.getCategory().name()
                            + " was passed.");
        }

        //将ObjectInspector 对象强制转换为PrimitiveObjectInspector
        PrimitiveObjectInspector inputOI = (PrimitiveObjectInspector) oi;
        //getCategory()方法获取PrimitiveObjectInspector 对象的类型
        System.out.println("getCategory()方法获取PrimitiveObjectInspector 对象的类型");
        System.out.println(inputOI.getCategory().name());
        if (inputOI.getPrimitiveCategory() != PrimitiveObjectInspector.PrimitiveCategory.STRING){
            throw new UDFArgumentTypeException(0,
                    "Argument must be String, but "
                            + inputOI.getPrimitiveCategory().name()
                            + " was passed.");
        }

        return new TotalNumOfLettersEvaluator();
    }
    //定义一个新的类,用于继承GenericUDAFEvaluator,来使用UDAF操作
    public static class TotalNumOfLettersEvaluator extends GenericUDAFEvaluator {

        PrimitiveObjectInspector inputOI;
        ObjectInspector outputOI;
        PrimitiveObjectInspector integerOI;

        int total = 0;
        //每个子类都应该覆盖这个函数
        //子类应该调用super。初始化(m,参数)以获得模式设置。
        // 确定各个阶段输入输出参数的数据格式ObjectInspectors
        //这是我非常想说的类,init()他会根据model值的不同来决定输入的类型和输出的类型,非常的灵活。
        // 并且注意,init()的调用不是单次的,是多次的。
        @Override
        public ObjectInspector init(Mode m, ObjectInspector[] parameters)
                throws HiveException {
            //assert 断言   使用true 继续执行
            assert (parameters.length == 1);
            //java中的super关键字是一个引用变量,用于引用父类对象
            super.init(m, parameters);

            //map阶段读取sql列,输入为String基础数据格式
            if (m == Mode.PARTIAL1 || m == Mode.COMPLETE) {
                inputOI = (PrimitiveObjectInspector) parameters[0];
            } else {
                //其余阶段,输入为Integer基础数据格式
                integerOI = (PrimitiveObjectInspector) parameters[0];
            }

            // 指定各个阶段输出数据格式都为Integer类型
            outputOI = ObjectInspectorFactory.getReflectionObjectInspector(Integer.class,
                    ObjectInspectorFactory.ObjectInspectorOptions.JAVA);
            return outputOI;

        }

        /**
         * 存储当前字符总数的类
         */
        //AggregationBuffer 允许我们保存中间结果,通过定义我们的buffer,
        // 我们可以处理任何格式的数据,在代码例子中字符总数保存在AggregationBuffer 。
        static class LetterSumAgg implements AggregationBuffer {
            int sum = 0;
            void add(int num){
                sum += num;
            }
        }
        // 保存数据聚集结果的类
        @Override
        public AggregationBuffer getNewAggregationBuffer() throws HiveException {
            LetterSumAgg result = new LetterSumAgg();
            return result;
        }
        // 重置聚集结果
        @Override
        public void reset(AggregationBuffer agg) throws HiveException {
            LetterSumAgg myagg = new LetterSumAgg();
        }

        private boolean warned = false;
        // map阶段,迭代处理输入sql传过来的列数据
        // 注意这里的迭代,当map阶段从表中读取一行时,就会调用一次iterate()方法,如果存在多行,就会调用多次。
        @Override
        public void iterate(AggregationBuffer agg, Object[] parameters)
                throws HiveException {
            assert (parameters.length == 1);
            if (parameters[0] != null) {
                LetterSumAgg myagg = (LetterSumAgg) agg;
                //通过基本数据类型OI解析Object p的值
                Object p1 = ((PrimitiveObjectInspector) inputOI).getPrimitiveJavaObject(parameters[0]);
                myagg.add(String.valueOf(p1).length());
            }
        }

        // map与combiner结束返回结果,得到部分数据聚集结果
        @Override
        public Object terminatePartial(AggregationBuffer agg) throws HiveException {
            LetterSumAgg myagg = (LetterSumAgg) agg;
            total += myagg.sum;
            return total;
        }
        // combiner合并map返回的结果,还有reducer合并mapper或combiner返回的结果。
        @Override
        public void merge(AggregationBuffer agg, Object partial)
                throws HiveException {
            if (partial != null) {

                LetterSumAgg myagg1 = (LetterSumAgg) agg;

                Integer partialSum = (Integer) integerOI.getPrimitiveJavaObject(partial);

                LetterSumAgg myagg2 = new LetterSumAgg();

                myagg2.add(partialSum);
                myagg1.add(myagg2.sum);
            }
        }
        // reducer阶段,输出最终结果
        @Override
        public Object terminate(AggregationBuffer agg) throws HiveException {
            LetterSumAgg myagg = (LetterSumAgg) agg;
            total = myagg.sum;
            return myagg.sum;
        }

    }

}