目錄:
一、繼承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、close
。initialize方法不是抽象方法不用實現,但是該方法需要重寫,不然會報錯。
- initialize初始化:UDTF首先會調用initialize方法,此方法返回UDTF的返回行的信息(返回個數,類型,名稱)。initialize針對任務調一次
- process:初始化完成後,會調用process方法,對傳入的參數進行處理,可以通過forword()方法把結果寫出。
process
傳入一行數據寫出去多次
,與mapreduce中的map方法很像,也是一行一行的數據傳入,傳入一行數據輸出多行數據,如:mapreduce單詞計數
。process針對每行數據調用一次該方法
- process:初始化完成後,會調用process方法,對傳入的參數進行處理,可以通過forword()方法把結果寫出。
- 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作用是定義輸出字段的列名、和輸出字段的數據類型,重寫該方法時裏面有一些知識點需要我們記
- 在定義輸出字段
(fieldNames)
的數據類型(ieldOIs)
時,該處定義的數據類型跟常用的Java數據類型不一致,需要格外去記。
比如string
數據類型這裏用的是javaStringObjectInspector
;int
數據類型這裏用的是javaIntObjectInspector
- 在定義輸出字段
- 輸出的字段名是一個集合,而不是一個字段。也就是炸裂這個方法可以輸出多個列。如:hive中explode對數組炸裂時返回一個字段,explode對map數據炸裂時返回2個字段.
- 輸出的列數據類型也是一個集合。
- 返回列字段名和列數據類型時,是返回的是一個工廠數據類型
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 {
}
}
後記
最後文章裏面,還有很多描述不清楚的地方,以及我不明白的地方,大家也可以去看看其他的文章。