Hive自定义UDF、UDTF(initialize部分源码解析)


这里分析Hive中部分UDF及UDTF函数的源码并依此进行自定义


1.UDF


这里UDF以Upper方法为例

此方法会将传入的字符串变为大写后返回


代码如下:


该类主要继承了GenericUDF类,该类有三个需要重写的方法

分别为:

1.public ObjectInspector initialize(ObjectInspector[] arguments)

该方法是UDF方法的初始化方法,主要用于验证输入参数的数据类型及输入参数的个数,返回值为ObjectInspector类型,该类型为Hive内部的类型监视器类,可以将它理解为Hive各种数据类型在java中对应的各种类的监视器。在这里传入的方法参数就是对应hive类型不同ObjectInspector类的子类,我们可以通过传入的参数来判断传入数据的类型。而返回值用于限定UDF方法对应的返回类型,比如UDF方法需要返回String类型对象,那么这里就要返回对应ObjectInspector对应String类型的子类监视器。

2.public Object evaluate(DeferredObject[] arguments)

主要的逻辑方法,当UDF被调用时,对每行的数据进行逻辑处理,参数为DeferredObject类型,可以将它理解为对应Hive中UDF传入参数的类型,在这里我们可以发现Java中并没有类似直接对应Hive的基本数据类型,而是有对应Hive各种类型的监视器(ObjectInspector类),以及对应参数的(DeferredObject类),这样就把判断和数据处理解耦了,在处理数据之前即调用evaluate方法前,initialize方法首先对传入数据的类型进行判断,判断通过后才会执行evaluate方法进行逻辑处理。

需要注意的是返回值必须是WritableComparable类型,即MR中key必须实现的接口类型。


3.public Stringget DisplayString(String[] children)

展示方法,主要用于打印UDF的一些基本信息,类似Log4j的功能。





public ObjectInspector initialize(ObjectInspector[] arguments)


该方法首先进行了两次判断,第一次判断参数个数是否为1,不为则抛出异常。(54-57行)

第二次判断传入的参数是否为Hive的基本数据类型,判断的依据是通过ObjectInspector监视器类获取传入参数的数据类型与枚举类型的Category.PRIMITIVE做比较。

(59-62行)

(这里可以稍微看下Category的枚举类型


PRIMITIVE对应Hive的基本数据类型,其他的List,Map等则对应Hive的非基本数据类型。)

接着是获取StringConverter类型转换器,后用于Hive基本类型的String类型转换。传入的参数为UDF方法参数的类型监视器,表示转换器用于该类型转String类型的数据转换,可以看到这里并没有限定类型必须为String类型,所以我可以推断出Upper方法在Hive中对int等基本数据类型甚至是boolean类型一样可以使用(65行)

接着就是通过参数的类型监视器获取到传入参数的基本数据类型,并通过switch来决定返回不同种类的监视器,这一点我们就可以看出如果UDF参数为char则最终返回char,如果为varchar则最终返回varchar,如果为其他(如int)则最终返回String。(case中代码)

需要注意的是char和varchar类型需要特殊处理,与String不同的是确定一个char或varchar的返回类型还需要同时确定返回的char或varchar字符串的长度,这就需要通过TypeInfo读取传入的参数监视器获取对应的长度,并以此确定返回值中char和varchar的长度


最后则是根据输入的类型获取不同的StringHelper(91行)

如果说StringConverter是用于将evaluate中的参数转化为String的工具类,那么StringHelper则是用于将java的基本数据类型转化为MR可以序列化的WritableComparable的工具类。这两个类都将在evaluate方法中用到。


public Objectevaluate(DeferredObject[] arguments)





逻辑很简单,我就不讲了,需要注意的是StringConverter和StringHelper两个类。

其实如果传入的Hive类型为String,其实并不需要使用StringConverter进行转换,直接arguments[0].get()获取到的就是String类型。

但是如果传入的参数比较特殊为Float,Byte等特殊类型,使用StringConverter就能帮我们把它们转化为String类型。

这里再强调一下,StringConverter是在initialize初始化方法执行时传入了UDF的数据类型,所以在evaluate中我们使用它的时候,它已经知道我们需要将什么数据类型转化为String了。

它内部的逻辑:


oi即为初始化时传入的监视器类型


StringHelper则帮助我们把字符串等基本数据类型转换为各种MR的可序列化Key类型,这里是将String类型转化为MR中的Text类型。

需要注意的是,与StringConverter类似,StringHelper也在初始化方法中传入了PrimitiveCategory对应hive的基本数据类型。

比如你设定了UDF的返回类型为PrimitiveObjectInspector.PrimitiveCategory.STRING;,那么这里的type就是String,只能帮你把String转化为Text类型。


(我在网课上看到的老师写的代码当需要返回int时并没有进行包装而是直接返回java的int类型,而源码中类似的int类型都是通过intWritible包装后返回,原因大概是因为老师选择的返回类型为JavaStringObjectInspector,而源码里是writableStringObjectInspector及其他类型,找了挺久没找到相关资料,但是应该与hive的serde过程有关,可能非writable的类型无法进行下一步Operator?)


public String getDisplayString(String[] children)

该方法可以不写,但是必须得返回空字符串!否则会报空指针。害我忙活半小时,呸。


下面是我自己的自定义UDF

实现把首字母大写的功能。

这里因为代码差不多看懂了,所以该复制的复制,该改的改,一点都没有不好意思,说实话我看源码就是为了复制起来不心虚,嘿嘿。

关键的地方都给了注释


源码的upper方法中返回类型会根据传入类型有关,而我这里写死了不管是什么传入类型,一律以String输出。



evalute方法,就改了一行代码。


UDTF

这里稍微搂一眼UDTF

一样找个方法来看,就找最常用的explode

主要需要重写两个方法,

public StructObjectInspector initialize(ObjectInspector[] args)

该方法对应UDF的initialize方法

public void process(Object[] o)

该方法对应UDF的evaluate方法,为主要逻辑方法。



相信有了上面UDF的基础,这里应该没什么看不懂的了,拿到传入参数的监视器,通过监视器的类型判断参数类型为List还是Map,如果不为这俩直接报错。

这里主要的只有一点,就是该方法的返回类型为StructObjectInspector,而UDF方法这里需要返回ObjectInspector即对应evaluate的最终返回类型。

StructObjectInspector的创建方式很死,正常只能通过ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs)方式创建

其构造函数的两个参数分别为两个数组,一个数组fieldNames里面的内容是该字段的默认列名,也就是我们查询出来之后改列的列名,当然如果我们手动设置的话会将其覆盖。

另一个数组fieldOIs的参数是explode方法将集合炸裂后可能形成多列,每列结果在process方法中的返回类型。比如Map集合就有key和value,炸裂后会形成两列的多行数据,我们需要分别指定每列数据的类型,这也是UDTF和UDF最大的不同之处。

在上面我们可以看到initialize方法中拿到参数的类型监视器后对其进行switch判断,

如果为List则设置列名为col,并且设置列的类型为((ListObjectInspector)inputOI).getListElementObjectInspector(),其实就是通过List类型监视器拿到该List内元素的类型,如果是字符串集合这里就拿到String的对应监视器类,如果是Int这里就拿到Int的对应监视器类。

如果为Map则设置两个列的默认列名分别为key和value,并且通过Map的监视器类分别拿到key和value对应的监视器类型进行设置,与上面的List监视器异曲同工。


public void process(Object[] o)



主要需要注意的只有一点,就是UDF只需要返回一个值,而UDTF需要返回多行甚至多列值,这里就没办法仅仅通过return来直接返回结果。而是使用GenericUDTF的forward方法来返回结果。

这里它正好有List和Map的对比,我们可以看到forward方法的参数为数组,如果你UDTF的结果为两列,那么该数组需包含至少两个元素,如果为一列,则一个数组只需要包含一个元素。


总结

代码比SQL有意思多了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容