一行输入,返回一个结果
可以满足日常工作中的大部分需求
UDF的实现方法
Hive 提供了两个实现 UDF 的方式:
第一种:继承 UDF 类
- 优点:
- 实现简单
- 支持Hive的基本类型、数组和Map
- 支持函数重载
- 缺点:
- 逻辑较为简单,只适合用于实现简单的函数
这种方式编码少,代码逻辑清晰,可以快速实现简单的UDF
第二种:继承 GenericUDF 类
- 优点:
- 支持任意长度、任意类型的参数
- 可以根据参数个数和类型实现不同的逻辑
- 可以实现初始化和关闭资源的逻辑(initialize、close)
- 缺点:
- 实现比继承UDF要复杂一些
与继承 UDF 相比,GenericUDF 更加灵活,可以实现更为复杂的函数
关于两者的选择
如果函数具有以下特点,优先继承 UDF 类:
- 逻辑简单,比如英文转小写函数
- 参数和返回值类型简单,都是Hive的基本类型、数组或Map
- 没有初始化或关闭资源的需求
否则考虑继承 GenericUDF 类
接下来介绍这两种实现方式的具体步骤
继承 UDF 类
第一种方式的代码实现最为简单,只需新建一个类 继承UDF,然后编写 evaluate() 即可
import org.apache.hadoop.hive.ql.exec.UDF;
/**
* 继承 org.apache.hadoop.hive.ql.exec.UDF
*/
public class SimpleUDF extends UDF {
/**
* 编写一个函数,要求如下:
* 1. 函数名必须为 evaluate
* 2. 参数和返回值类型可以为:Java基本类型、Java包装类、org.apache.hadoop.io.Writable等类型、List、Map
* 3. 函数一定要有返回值,不能为 void
*/
public int evaluate(int a, int b) {
return a + b;
}
/**
* 支持函数重载
*/
public Integer evaluate(Integer a, Integer b, Integer c) {
if (a == null || b == null || c == null)
return 0;
return a + b + c;
}
}
继承UDF类的方式非常简单,但还有一些需要注意的地方:
- evaluate() 方法并不是继承自 UDF 类(编写代码时名字容易打错,哈哈哈~)
- evaluate() 的返回值类型不能为 void(你这个函数返回值都没有,我要你干嘛?)
支持的参数和返回值类型
支持 hive基本类型、数组和Map
Hive基本类型
Java可以使用Java原始类型、Java包装类或对应的Writable类
PS:对于基本类型,最好不要使用 Java原始类型,当 null 传给 Java原始类型 参数时,UDF 会报错。Java包装类还可以用于null值判断
| Hive类型 | Java原始类型 | Java包装类 | hadoop.io.Writable |
|---|---|---|---|
| tinyint | byte | Byte | ByteWritable |
| smallint | short | Short | ShortWritable |
| int | int | Integer | IntWritable |
| bigint | long | Long | LongWritable |
| string | String | - | Text |
| boolean | boolean | Boolean | BooleanWritable |
| float | float | Float | FloatWritable |
| double | double | Double | DoubleWritable |
数组和Map
| Hive类型 | Java类型 |
|---|---|
| array | List |
| Map<K, V> | Map<K, V> |
继承 GenericUDF
这种方式最为灵活,但实现起来也比上一种方法要复杂一些
继承 GenericUDF 后,必须实现其三个方法:
- initialize()
- evaluate()
- getDisplayString()
initialize()
/**
* 初始化 GenericUDF,每个 GenericUDF 示例只会调用一次初始化方法
*
* @param arguments
* 自定义UDF参数的 ObjectInspector 实例
* @throws UDFArgumentException
* 参数个数或类型错误时,抛出该异常
* @return 函数返回值类型
*/
public abstract ObjectInspector initialize(ObjectInspector[] arguments)
throws UDFArgumentException;
initialize() 在函数在 GenericUDF 初始化时被调用一次,执行一些初始化操作,包括:
- 判断函数参数个数
- 判断函数参数类型
- 确定函数返回值类型
除此之外,用户在这里还可以做一些自定义的初始化操作,比如初始化HDFS客户端等
其一:判断函数参数个数
可通过 arguments 数组的长度来判断函数参数的个数
判断函数参数个数示例:
// 1. 参数个数检查,只有一个参数
if (arguments.length != 1) // 函数只接受一个参数
throw new UDFArgumentException("函数需要一个参数"); // 当自定义UDF参数与预期不符时,抛出异常
其二:判断函数参数类型
ObjectInspector 可用于侦测参数数据类型,其内部有一个枚举类 Category,代表了当前 ObjectInspector 的类型
public interface ObjectInspector extends Cloneable {
public static enum Category {
PRIMITIVE, // Hive原始类型
LIST, // Hive数组
MAP, // Hive Map
STRUCT, // 结构体
UNION // 联合体
};
}
Hive原始类型又细分了多种子类型,PrimitiveObjectInspector 实现了 ObjectInspector,可以更加具体的表示对应的Hive原始类型
public interface PrimitiveObjectInspector extends ObjectInspector {
/**
* The primitive types supported by Hive.
*/
public static enum PrimitiveCategory {
VOID, BOOLEAN, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, STRING,
DATE, TIMESTAMP, BINARY, DECIMAL, VARCHAR, CHAR, INTERVAL_YEAR_MONTH, INTERVAL_DAY_TIME,
UNKNOWN
};
}
关于 PrimitiveCategory 的枚举类型就不一一解释了
参数类型判断示例:
// 2. 参数类型检查,参数类型为String
if (arguments[0].getCategory() != ObjectInspector.Category.PRIMITIVE // 参数是Hive原始类型
|| !PrimitiveObjectInspector.PrimitiveCategory.STRING.equals(((PrimitiveObjectInspector)arguments[0]).getPrimitiveCategory())) // 参数是Hive的string类型
throw new UDFArgumentException("函数第一个参数为字符串"); // 当自定义UDF参数与预期不符时,抛出异常
其三:确定函数返回值类型
initialize() 需要 return 一个 ObjectInspector 实例,用于表示自定义UDF返回值类型。initialize() 的返回值决定了 evaluate() 的返回值类型
ObjectInspector 的源码中,有这么一段注释,其大意是 ObjectInspector 的实例应该由对应的工厂类获取,以保证实例的单例等属性
/**
* An efficient implementation of ObjectInspector should rely on factory, so
* that we can make sure the same ObjectInspector only has one instance. That
* also makes sure hashCode() and equals() methods of java.lang.Object directly
* works for ObjectInspector as well.
*/
public interface ObjectInspector extends Cloneable { }
对于基本类型(byte、short、int、long、float、double、boolean、string),可以通过 PrimitiveObjectInspectorFactory 的静态字段直接获取
| Hive类型 | writable类型 | Java包装类型 |
|---|---|---|
| tinyint | writableByteObjectInspector | javaByteObjectInspector |
| smallint | writableShortObjectInspector | javaShortObjectInspector |
| int | writableIntObjectInspector | javaIntObjectInspector |
| bigint | writableLongObjectInspector | javaLongObjectInspector |
| string | writableStringObjectInspector | javaStringObjectInspector |
| boolean | writableBooleanObjectInspector | javaBooleanObjectInspector |
| float | writableFloatObjectInspector | javaFloatObjectInspector |
| double | writableDoubleObjectInspector | javaDoubleObjectInspector |
注意:基本类型返回值有两种:Writable类型 和 Java包装类型:
- 在 initialize 指定的返回值类型为 Writable类型 时,在 evaluate() 中 return 的就应该是对应的 Writable实例
- 在 initialize 指定的返回值类型为 Java包装类型 时,在 evaluate() 中 return 的就应该是对应的 Java包装类实例
Array、Map<K, V>等复杂类型,则可以通过 ObjectInspectorFactory 的静态方法获取
| Hive类型 | ObjectInspectorFactory的静态方法 | evaluate()返回值类型 |
|---|---|---|
| Array | getStandardListObjectInspector(T t) | List |
| Map<K, V> | getStandardMapObjectInspector(K k, V v); | Map<K, V> |
返回值类型为 Map<String, int> 的示例:
// 3. 自定义UDF返回值类型为 Map<String, int>
return ObjectInspectorFactory.getStandardMapObjectInspector(
PrimitiveObjectInspectorFactory.javaStringObjectInspector, // Key 是 String
PrimitiveObjectInspectorFactory.javaIntObjectInspector // Value 是 int
);
完整的 initialize() 函数如下:
/**
* 初始化 GenericUDF,每个 GenericUDF 示例只会调用一次初始化方法
*
* @param arguments
* 自定义UDF参数的 ObjectInspector 实例
* @throws UDFArgumentException
* 参数个数或类型错误时,抛出该异常
* @return 函数返回值类型
*/
@Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
// 1. 参数个数检查,只有一个参数
if (arguments.length != 1) // 函数只接受一个参数
throw new UDFArgumentException("函数需要一个参数"); // 当自定义UDF参数与预期不符时,抛出异常
// 2. 参数类型检查,参数类型为String
if (arguments[0].getCategory() != ObjectInspector.Category.PRIMITIVE // 参数是Hive原始类型
|| !PrimitiveObjectInspector.PrimitiveCategory.STRING.equals(((PrimitiveObjectInspector)arguments[0]).getPrimitiveCategory())) // 参数是Hive的string类型
throw new UDFArgumentException("函数第一个参数为字符串"); // 当自定义UDF参数与预期不符时,抛出异常
// 3. 自定义UDF返回值类型为 Map<String, int>
return ObjectInspectorFactory.getStandardMapObjectInspector(
PrimitiveObjectInspectorFactory.javaStringObjectInspector, // Key 是 String
PrimitiveObjectInspectorFactory.javaIntObjectInspector // Value 是 int
);
}
evaluate()
核心方法,自定义UDF的实现逻辑
代码实现步骤可以分为三部分:
- 参数接收
- 自定义UDF核心逻辑
- 返回处理结果
第一步:参数接收
evaluate() 的参数就是 自定义UDF 的参数,
/**
* Evaluate the GenericUDF with the arguments.
*
* @param arguments
* The arguments as DeferedObject, use DeferedObject.get() to get the
* actual argument Object. The Objects can be inspected by the
* ObjectInspectors passed in the initialize call.
* @return The
*/
public abstract Object evaluate(DeferredObject[] arguments)
throws HiveException;
通过源码注释可知,DeferedObject.get() 可以获取参数的值
/**
* A Defered Object allows us to do lazy-evaluation and short-circuiting.
* GenericUDF use DeferedObject to pass arguments.
*/
public static interface DeferredObject {
void prepare(int version) throws HiveException;
Object get() throws HiveException;
};
再看看 DeferredObject 的源码,DeferedObject.get() 返回的是 Object,传入的参数不同,会是不同的Java类型,以下是Hive常用参数类型对应的Java类型
对于Hive基本类型,传入的都是 Writable类型
| Hive类型 | Java类型 |
|---|---|
| tinyint | ByteWritable |
| smallint | ShortWritable |
| int | IntWritable |
| bigint | LongWritable |
| string | Text |
| boolean | BooleanWritable |
| float | FloatWritable |
| double | DoubleWritable |
| Array | ArrayList |
| Map<K, V> | HashMap<K, V> |
参数接收示例:
// 只有一个参数:Map<String, int>
// 1. 参数为null时的特殊处理
if (arguments[0] == null)
return ...
// 2. 接收参数
Map<Text, IntWritable> map = (Map<Text, IntWritable>)arguments[0].get();
第二步:自定义UDF核心逻辑
获取参数之后,到这里就是自由发挥了~
第三步:返回处理结果
这一步和 initialize() 的返回值一一对应
基本类型返回值有两种:Writable类型 和 Java包装类型:
- 在 initialize 指定的返回值类型为 Writable类型 时,在 evaluate() 中 return 的就应该是对应的 Writable实例
- 在 initialize 指定的返回值类型为 Java包装类型 时,在 evaluate() 中 return 的就应该是对应的 Java包装类实例
Hive数组和Map的返回值类型如下:
| Hive类型 | Java类型 |
|---|---|
| Array<T> | List<T> |
| Map<K, V> | Map<K, V> |
getDisplayString()
getDisplayString() 返回的是 explain 时展示的信息
/**
* Get the String to be displayed in explain.
*/
public abstract String getDisplayString(String[] children);
注意:这里不能return null,否则可能在运行时抛出空指针异常,而且这个出现这个问题还不容易排查~
ERROR [b1c82c24-bfea-4580-9a0c-ff47d7ef4dbe main] ql.Driver: FAILED: NullPointerException null
java.lang.NullPointerException
at java.util.regex.Matcher.getTextLength(Matcher.java:1283)
...
at org.apache.hadoop.util.RunJar.main(RunJar.java:136)
close()
资源关闭回调函数
不是抽象方法,可以不实现
/**
* Close GenericUDF.
* This is only called in runtime of MapRedTask.
*/
@Override
public void close() throws IOException { }
自定义GenericUDF完整示例
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.io.Text;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class SimpleGenericUDF extends GenericUDF {
@Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
// 1. 参数个数检查
if (arguments.length != 1) // 函数只接受一个参数
throw new UDFArgumentException("函数需要一个参数"); // 当自定义UDF参数与预期不符时,抛出异常
// 2. 参数类型检查
if (arguments[0].getCategory() != ObjectInspector.Category.PRIMITIVE // 参数是Hive原始类型
|| !PrimitiveObjectInspector.PrimitiveCategory.STRING.equals(((PrimitiveObjectInspector)arguments[0]).getPrimitiveCategory())) // 参数是Hive的string类型
throw new UDFArgumentException("函数第一个参数为字符串"); // 当自定义UDF参数与预期不符时,抛出异常
// 3. 自定义UDF返回值类型为 Map<String, int>
return ObjectInspectorFactory.getStandardMapObjectInspector(
PrimitiveObjectInspectorFactory.javaStringObjectInspector, // Key 是 String
PrimitiveObjectInspectorFactory.javaIntObjectInspector // Value 是 int
);
}
@Override
public Object evaluate(DeferredObject[] arguments) throws HiveException {
// 1. 参数接收
if (arguments[0] == null)
return new HashMap<>();
String str = ((Text) arguments[0].get()).toString();
// 2. 自定义UDF核心逻辑
// 统计字符串中每个字符的出现次数,并将其记录在Map中
Map<String, Integer> map = new HashMap<>();
for (char ch : str.toCharArray()) {
String key = String.valueOf(ch);
Integer count = map.getOrDefault(key, 0);
map.put(key, count + 1);
}
// 3. 返回处理结果
return map;
}
@Override
public String getDisplayString(String[] children) {
return "这是一个简单的测试自定义UDF~";
}
}