hive中concat_ws的秘密
1. 背景
在hive中,concat_ws有两种用法:
concat_ws("|", array('a', 'b')); -- 输出a|bconcat_ws('|', 'a', 'b');-- 输出a|b
其中,第一个参数是其它字符串的分隔符,concat_ws有个比较好用的特点,就是会自动跳过值为null的字符串(注意:空字符串不会跳过)。例如:
concat_ws('|', 'a', null, 'b');-- 输出a|b
但是在工作中,我遇到了一个奇怪的问题:
concat_ws('|', 'a', null, 'b');-- 输出a|bconcat_ws('|', array('a', null, 'b'));-- 输出a|null|b
这就奇怪了,不是说concat_ws会忽略所有null吗,为什么这里非但没忽略,还输出了“null”字符串?
2. 源码探究
2.1 concat_ws源码
实在按奈不住好奇心,于是去扒了一下concat_ws的UDF源码:
@Overridepublic 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++) {// 判断所取的值是不是nullif (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++) {// 与外循环同样的处理逻辑,唯一的区别就是没有判断nullif (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
字符串是哪里来的呢?
2.2 stringbuilder源码
终于到了最后的阶段,打开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字符串的来源。