在hive中,concat_ws有两种用法:
concat_ws("|", array('a', 'b'));
-- 输出a|b
concat_ws('|', 'a', 'b');
-- 输出a|b
其中,第一个参数是其它字符串的分隔符,concat_ws有个比较好用的特点,就是会自动跳过值为null的字符串(注意:空字符串不会跳过)。例如:
concat_ws('|', 'a', null, 'b');
-- 输出a|b
但是在工作中,我遇到了一个奇怪的问题:
concat_ws('|', 'a', null, 'b');
-- 输出a|b
concat_ws('|', array('a', null, 'b'));
-- 输出a|null|b
这就奇怪了,不是说concat_ws会忽略所有null吗,为什么此非但没遇到,还输出了“null”字符串?
实在按奈不住好奇心,于是去扒了一下concat_ws的UDF源码:
@Override
public Object evaluate(DeferredObject[] arguments) throws HiveException {
if (arguments[0].get() == null) {
return null;
}
// 获取分隔符
String separator = PrimitiveObjectInspectorUtils.getString(
arguments[0].get(), (PrimitiveObjectInspector)argumentOIs[0]);
StringBuilder sb = new StringBuilder();
boolean first = true;
for (int i = 1; i < arguments.length; i++) {
// 判断所取的值是不是null
if (arguments[i].get() != null) {
// 如果是第一个字符串,则不加分隔符,否则加
if (first) {
first = false;
} else {
sb.append(separator);
}
// 如果第二个参数是List,也就是 concat_ws('|', array()) 这种使用类型
if (argumentOIs[i].getCategory().equals(Category.LIST)) {
Object strArray = arguments[i].get();
ListObjectInspector strArrayOI = (ListObjectInspector) argumentOIs[i];
boolean strArrayFirst = true;
for (int j = 0; j < strArrayOI.getListLength(strArray); j++) {
// 与外循环同样的处理逻辑,唯一的区别就是没有判断null
if (strArrayFirst) {
strArrayFirst = false;
} else {
sb.append(separator);
}
sb.append(strArrayOI.getListElement(strArray, j));
}
} else {
// 如果第二个参数不是List,则一个一个append,中间穿插
sb.append(PrimitiveObjectInspectorUtils.getString(
arguments[i].get(), (PrimitiveObjectInspector)argumentOIs[i]));
}
}
}
resultText.set(sb.toString());
return resultText;
}
在这里,可以清晰的看到concat_ws的处理流程。在取每个参数值后,会有个arguments[i].get() != null
,判断是否为null。如果所取的参数类型为array,就进入for循环,取出array中的每个值。
与外层循环不同的是,array中的循环并没有做null判断处理,而是直接append。
因此,如果使用concat_ws('|', array())
这种模式,array中的null值并不会被跳过。
但是至此还有一个疑问,最后输出结果中的null
字符串是哪里来的呢?
终于到了最后的阶段,打开stringbuilder的源码,如果append进来的String为null,最后会走到appendNull方法。
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
至此,终于搞清楚了concat_ws中null字符串的来源。