前言
学习udf的时候,程序员给人感觉是知道会快些,这个答案是肯定的,因为常规的视角都是udf驱动效率,但是程序员的世界是反过来的–程序员驱动udf的效率。
udf的几座大山
学习udf函数几乎需要回答下面几个问题:
- 我要做这个操作有什么函数
- 这个函数怎么使用
- 我咋自己去写一个函数
- hive里面还有哪些骚操作
源码引入
写程序肯定是需要源码的,hive是开源的,源码可以随时查阅,很多人因为编译不过就放弃,其实源码只要可以导入idea搜索就可以了,当然能调试更好,有了源码,我们就可以各种搜索,这个就是我们学习宝库!
函数的入口
udf其实就是一堆堆的类执行函数,一般常规这种灵活的扩展性,hive是提供了注册进制来做的,我们需要找到我们的注册类:
package org.apache.hadoop.hive.ql.exec; ......若干引入包 /** * FunctionRegistry. */ public final class FunctionRegistry { private static final Logger LOG = LoggerFactory.getLogger(FunctionRegistry.class); ......若干代码
我们可以看到源码部分都是很有规律的函数引入,第一个参数便是函数名,第二个参数便是实现类了,不光如此,udf注册的时候在代码上面做了一个很好的分类,比如字符操作相关的会放在一起:
system.registerGenericUDF("concat", GenericUDFConcat.class); system.registerUDF("substr", UDFSubstr.class, false); ......
时间处理相关的,也会放在一起
system.registerUDF("day", UDFDayOfMonth.class, false); system.registerUDF("dayofmonth", UDFDayOfMonth.class, false); ......
我们按照有规律的排布,可以很方便找到我们要的函数。
函数的实现类
在函数头部有一段描述,比较有耐心的程序员会把这段写得很详细,这段在函数注册时候我们可以通过desc 命令看得到。
hive> desc function concat; Begin to execute: desc function concat; OK concat(str1, str2, ... strN) - returns the concatenation of str1, str2, ... strN or concat(bin1, bin2, ... binN) - returns the concatenation of bytes in binary data bin1, bin2, ... binN Time taken: 0.029 seconds, Fetched: 1 row(s)
测试代码
hive这种代码都是会经过大量的测试,测试代码中我们有个很关键的东西就是里面会直接写这个函数的用法,因为测试的是sql,源码中放在了.g的文件中,我们找到(通过搜索去找):
关于udf的第一手使用基本来自这个地方,在这里会覆盖各种场景和用法,基本在这个地方摸索就可以了。
反射函数
反射操作其实是程序员喜欢,但是市面上听说很少的操作,我们直接从源码找到这几个函数:
system.registerGenericUDF("reflect", GenericUDFReflect.class); system.registerGenericUDF("reflect2",GenericUDFReflect2.class; system.registerGenericUDF("java_method",GenericUDFReflect.clas;
我们在测试代码中找到对应的使用方法:
reflect使用
我们捡关键的部分:
SELECT reflect("java.lang.String", "valueOf", 1), reflect("java.lang.String", "isEmpty"), reflect("java.lang.Math", "max", 2, 3), reflect("java.lang.Math", "min", 2, 3), reflect("java.lang.Math", "round", 2.5D), round(reflect("java.lang.Math", "exp", 1.0D), 6), reflect("java.lang.Math", "floor", 1.9D), reflect("java.lang.Integer", "valueOf", key, 16) FROM src tablesample (1 rows)
这种操作基本就秒懂,所以这个udf瞬间学会了不是么,当然,我们还是稍加解释。
reflect其实就是通过反射的形式去调用我们java中的代码,hive是java写的,调用java代码也完全不是事,这样子操作其实意味着,我们在程序中写的函数可以通过这种方式引用,一般情况下我们jdk在lang下的包我们不会主动去引用,实际上来说我们主动写上引用也是ok的:
效果一样的代码:
System.out.println(String.valueOf("2")); System.out.println(Math.max(2, 3));
System.out.println(java.lang.String.valueOf("2")); System.out.println(java.lang.Math.max(2,3));
到了udf上面反射是要加全路径的:
比如一段求最大值的操作
select reflect("java.lang.Math", "max", 2, 3); 3
reflect2
这个函数做啥的,因为我们其实已经有了reflect函数,我们还是找的测试的代码:
SELECT key, reflect2(key, "byteValue"), reflect2(key, "shortValue"), ..... reflect2(value, "concat", "_concat"), reflect2(value, "contains", "86"), reflect2(value, "startsWith", "v"), reflect2(value, "endsWith", "6"), reflect2(value, "equals", "val_86"), reflect2(value, "equalsIgnoreCase", "VAL_86") ts, reflect2(ts, "getYear"), reflect2(ts, "getMonth"), reflect2(ts, "getDay"), ..... FROM (select cast(key as int) key, value, cast('2013-02-15 19:41:20' as timestamp) ts from src) a LIMIT 5;
事实上,就算看了这个测试代码其实也不大清楚做啥的,我们找到源码上面去看看,源码在对应类:
org.apache.hadoop.hive.ql.udf.generic.GenericUDFReflect2中,从138行开始我们找到关键逻辑:
...... switch (returnOI.getPrimitiveCategory()) { case VOID: return null; case BOOLEAN: ((BooleanWritable)returnObj).set((Boolean)result); return returnObj; case BYTE: ((ByteWritable)returnObj).set((Byte)result); return returnObj; case SHORT: ((ShortWritable)returnObj).set((Short)result); return returnObj; case INT: ((IntWritable)returnObj).set((Integer)result); return returnObj; ......
根据前面的函数调用情况,我们其实可以看到,这个函数其实是对应的数据类型不同可以调用到不同的方法,比如字符串操作的startwith,日期函数对应的getYear,这个函数是可以根据类型来适配的。
我们操作一把:对应日期类型提前年月日的操作:
select Reflect2(ts, "getYear"), reflect2(ts, "getMonth"), reflect2(ts, "getDay"), reflect2(ts, "getHours"), reflect2(ts, "getMinutes"), reflect2(ts, "getSeconds") from (select cast('2013-02-15 19:41:20' as timestamp) ts ) t; 113 1 5 19 41 20
这里发现了我们的2013的年结果是113,月份是1,这个我们也是要看源码去解释:
我们看到年份其实是从1900开始计算的,月份其实是从0开始算的,计算器一点,我们没问题。
再来一个字符串替换操作:
select reflect2("原始值[val]", "replace", "val", "新值") 原始值新值
java_method
反射函数最后的一个函数,我们还是按照老路子去找,我们找到测试的代码:
SELECT java_method("java.lang.String", "valueOf", 1), java_method("java.lang.String", "isEmpty"), java_method("java.lang.Math", "max", 2, 3), java_method("java.lang.Math", "min", 2, 3), java_method("java.lang.Math", "round", 2.5D), round(java_method("java.lang.Math", "exp", 1.0D), 6), java_method("java.lang.Math", "floor", 1.9D) FROM src tablesample (1 rows);
这个看了之后发现好像和reflect也没啥差别啊,我们只能心里觉得一样,但是总每个说服力,我们继续寻寻觅觅,发现这么一句话:
这架势,百度有道谷歌啥的通通来一遍,我们java_method和reflect其实是同义词的概念,我摸一下注册代码,发生其实对应的实现类其实就是同一个!
不用看了,玩跑跑去~