Hive反射函數的使用-程序員是怎麼學UDF函數的

前言

學習udf的時候,程序員給人感覺是知道會快些,這個答案是肯定的,因爲常規的視角都是udf驅動效率,但是程序員的世界是反過來的–程序員驅動udf的效率。

udf的幾座大山

學習udf函數幾乎需要回答下面幾個問題:

  1. 我要做這個操作有什麼函數
  2. 這個函數怎麼使用
  3. 我咋自己去寫一個函數
  4. 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其實是同義詞的概念,我摸一下注冊代碼,發生其實對應的實現類其實就是同一個!
在這裏插入圖片描述
不用看了,玩跑跑去~
在這裏插入圖片描述

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