hive自定义UDTF函数,步骤讲解

目录:
一、继承GenericUDTF抽象类
二、重写方法initialize()
三、实现抽象方法process()
四、实现抽象方法close()
五、自定义将一行字符串转多行代码

UDTF(User-Defined Table-Generating Functions)是一进多出函数,如hive中的explode()函数。
在学习自定义UDTF函数时,一定要知道hive中的UDTF函数如何使用,不会的先看这篇文章:hive中UDTF函数explode详解 + explode与lateral view 3套案例练习

自定义UDF函数步骤如下:

自定义函数、实现UDTF一进多出功能,我们主要关心的是要继承什么类,实现什么方法。
1)继承org.apache.hadoop.hive.ql.udf.generic.GenericUDTF
2)重写initialize、process、close方法

备注:我们在继承这个类的时候,只需要关心它能实现什么功能、我们需要处理什么业务逻辑。就像在使用sql函数也是这样,我以前还纠结为什么它可以实现这样的功能。
不过继承的这个类可以实现什么功能,怎么使用一定一定要清楚、熟练掌握。

一、继承GenericUDTF抽象类

继承GenericUDTF抽象类时,我们需要重写initialize方法、并实现2个抽象方法(process、close).
在Alt + Enter回车时,只提示我们实现两个方法抽象方法process、closeinitialize方法不是抽象方法不用实现,但是该方法需要重写,不然会报错。

    1. initialize初始化:UDTF首先会调用initialize方法,此方法返回UDTF的返回行的信息(返回个数,类型,名称)。initialize针对任务调一次
    1. process:初始化完成后,会调用process方法,对传入的参数进行处理,可以通过forword()方法把结果写出。
      process传入一行数据写出去多次,与mapreduce中的map方法很像,也是一行一行的数据传入,传入一行数据输出多行数据,如:mapreduce单词计数process针对每行数据调用一次该方法
    1. close:最后close()方法调用,对需要清理的方法进行清理,close()方法针对整个任务调一次
public abstract class GenericUDTF {
…
…
    /** @deprecated */
    @Deprecated
    public StructObjectInspector initialize(ObjectInspector[] argOIs) throws UDFArgumentException {
        throw new IllegalStateException("Should not be called directly");
    }

    public abstract void process(Object[] var1) throws HiveException;

    public abstract void close() throws HiveException;
…
…
}

二、重写方法initialize()

initialize方法是针对整个任务调一次,initialize作用是定义输出字段的列名、和输出字段的数据类型,重写该方法时里面有一些知识点需要我们记

    1. 在定义输出字段(fieldNames)数据类型(ieldOIs)时,该处定义的数据类型跟常用的Java数据类型不一致,需要格外去记。
      比如string数据类型这里用的是javaStringObjectInspector;int 数据类型这里用的是javaIntObjectInspector
    1. 输出的字段名是一个集合,而不是一个字段。也就是炸裂这个方法可以输出多个列。如:hive中explode对数组炸裂时返回一个字段,explode对map数据炸裂时返回2个字段.
    1. 输出的列数据类型也是一个集合。
    1. 返回列字段名和列数据类型时,是返回的是一个工厂数据类型ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs),记住就好了。
      这里语言描述有问题,底层的还没理解到,就是定义好输出的字段和字段数据类型,然后把这两个参数塞到getStandardStructObjectInspector方法里面去。
 @Override
    /**
     * 返回数据类型:StructObjectInspector
     * 定义输出数据的列名、和数据类型。
     */
    public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
        //fieldNames字段名,函数定义字段名,关心输入和输出。应该为输出的字段名

        List<String> fieldNames = new ArrayList<String>();//问题?为什么函数输出的字段名是一个集合,而不是一个字段?
        //也就是炸裂这个方法可以输出多个列,我们使用hive默认的explode函数炸裂的时候是炸裂一个列,
        //但是UDTF炸裂可以有多个列
        fieldNames.add("world");

        List<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>(); //类型,列输出类型
        fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);

        return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
    }

三、实现抽象方法process()

process方法是一行数据调用一次process方法,即有多少行数据就会调用多少次process方法。主要作用是对传入的每一行数据写出去多次,调用forward()将数据写入到一个缓冲区。

有2个点需要记住:

  • 1)在initialize初始化的时候,定义输出字段的数据类型是集合,我们调用forward()将数据写入到一个缓冲区,写入缓冲区的数据也要是集合
  • 2)如果只定义了一个集合,每次调用forward()写数据之前,需要将集合中的数据给清空。


    //数据的集合
    private List<String> dataList = new ArrayList<String>();

 /**
     * process(Object[] objects) 参数是一个数组,但是hive中的explode函数接受的是一个,一进多出
     * @param args
     * @throws HiveException
     */
    public void process(Object[] args) throws HiveException {
        //我们现在的需求是传入一个数据,在传入一个分割符

        //1.获取数据
        String data = args[0].toString();

        //2.获取分割符
        String splitKey = args[1].toString();

        //3.切分数据,得到一个数组
        String[] words = data.split(splitKey);

        //4.想把words里面的数据全部写出去。类似在map方法中,通过context.write方法
        // 定义是集合、写出去是一个string,类型不匹配,写出也要写出一个集合
        for (String word : words) {
            //5.将数据放置集合,EG:传入"hello,world,hdfs"---->写出需要写n次,hello\world
            dataList.clear();//清空数据集合

            dataList.add(word);

            //5.写出数据的操作
            forward(dataList);
        }
    }

四、实现抽象方法close()

这里没有io流的操作所以不需要关闭。

关于是否有IO流以及是否关闭IO流不清楚。

    public void close() throws HiveException {

    }

五、自定义将一行字符串转多行代码

package com.atguigu.udtf;

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.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;

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

/**
 * 类两边有一些阴影是抽象类,继承抽象类就要实现抽象方法
 */
public class SplitUDTF extends GenericUDTF {
    //数据的集合
    private List<String> dataList = new ArrayList<String>();

    @Override
    /**
     * 返回数据类型:StructObjectInspector
     * 定义输出数据的列名、和数据类型。
     */
    public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
        //fieldNames字段名,函数定义字段名,关心输入和输出。应该为输出的字段名

        List<String> fieldNames = new ArrayList<String>();//问题?为什么函数输出的字段名是一个集合,而不是一个字段?
        //也就是炸裂这个方法可以输出多个列,我们使用hive默认的explode函数炸裂的时候是炸裂一个列,
        //但是UDTF炸裂可以有多个列
        fieldNames.add("world");

        List<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>(); //类型,列输出类型
        fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
//        fieldOIs.add(PrimitiveObjectInspectorFactory.javaIntObjectInspector);

        return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
    }

    /**
     * process(Object[] objects) 参数是一个数组,但是hive中的explode函数接受的是一个,一进多出
     * @param args
     * @throws HiveException
     */
    public void process(Object[] args) throws HiveException {
        //我们现在的需求是传入一个数据,在传入一个分割符

        //1.获取数据
        String data = args[0].toString();

        //2.获取分割符
        String splitKey = args[1].toString();

        //3.切分数据,得到一个数组
        String[] words = data.split(splitKey);

        //4.想把words里面的数据全部写出去。类似在map方法中,通过context.write方法
        // 定义是集合、写出去是一个string,类型不匹配,写出也要写出一个集合
        for (String word : words) {
            //5.将数据放置集合,EG:传入"hello,world,hdfs"---->写出需要写n次,hello\world
            dataList.clear();//清空数据集合

            dataList.add(word);

            //5.写出数据的操作
            forward(dataList);
        }
    }

    public void close() throws HiveException {

    }
}

后记

最后文章里面,还有很多描述不清楚的地方,以及我不明白的地方,大家也可以去看看其他的文章。

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