2021SC@SDUSC
研究内容介绍本人负责的是负责的是将查询块QB转换成逻辑查询计划(OP Tree)
如下的代码出自apaceh-hive-3.1.2-src/ql/src/java/org/apache/hadoop/hive/ql/plan中,也就是我的分析目标代码。本周的研究计划是解析PlanMapper.java文件源码。
我们首先附上整个java文件的源码。
package org.apache.hadoop.hive.ql.plan.mapper;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import org.apache.hadoop.hive.ql.exec.Operator;
import org.apache.hadoop.hive.ql.optimizer.signature.OpTreeSignature;
import org.apache.hadoop.hive.ql.optimizer.signature.OpTreeSignatureFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
public class PlanMapper {
Set groups = new HashSet<>();
private Map
类内全局变量解析
Set
对于这个语句,我们来关注一下整个HashSet类。经过查阅资料,我们得知HashSet类是存在于java.util包中的类,也就是IDE自带的类。这是一个集合,而我们学过的数学知识告诉我们集合是不能重复的,因此HashSet中只能存储不重复的对象。对于HashSet来说,它是基于HashMap实现的,底层采用的是HashMap来存储元素。我们来看一下它的源码:
private transient HashMapmap; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); }
此外,虽然说是不能重复,但是说的是不能有相同类型的相同值,但是不同类型的相同值是可以重复的,比如1以及"1"就被视为不同的值。而且可以插入null,但只能插入一个null,即使是null也是不能重复的。最后,HashSet对插入的值会在内部自动排序,非常的人性化。
例如输入:
5,5,4,4,3,3,2,2,1,1,null,null
则会输出:
null,1,2,3,4,5
我们来看一下HashSet有哪些操作:
int size(); //获取set大小,null也算。 boolean isEmpty() ;//判断set是否为空 boolean contains(Object o);//判断元素是否存在 boolean add(E,e);//添加元素 boolean remove(Object o);//删除元素 void clear();//清空set所有元素
而对HashSet的遍历有多种方式,我们先来看一下第一种方式:
HashSetset=new HashSet (); set.add("5"); set.add("5"); set.add("4"); set.add("4"); set.add("3"); set.add("2"); set.add("1"); set.add(null); set.add(null); set.add("null"); for(String s:set){ System.out.print(s+","); }
输出:
null,1,2,3,4,null,5;//(一个是null,一个是字符串“null”)
我们再来看一下第二种方式:
HashSetset=new HashSet (); set.add("5"); set.add("5"); set.add("4"); set.add("4"); set.add("3"); set.add("2"); set.add("1"); set.add(null); set.add(null); set.add("null"); Iterator it=set.iterator(); while (it.hasNext()){ System.out.print(it.next()+","); }
输出:
null,1,2,3,4,null,5;//(一个是null,一个是字符串“null”)
我们再来看一下这个语句
private Map
其中 Map<>类我们已经接触过很多次了,一个键值对的集合。我们来关注一下这个CompositeMap<>类。我们看了一下对它的英文介绍:
CompositeMap modifies another Map. Add and remove operations use pluggable strategies. If no strategy is provided, addition and deletion are not supported.
CompositeMap类属于org.apache.commons.collections.map包,它修饰另一个Map。我们可以来看一下它的用法:
import org.apache.commons.collections.map.CompositeMap; //导入依赖的package包/类
public Object put(CompositeMap map, Map[] composited, Object key,
Object value) {
if (composited.length < 1) {
throw new UnsupportedOperationException(
"No composites to add elements to");
}
Object result = map.get(key);
if (result != null) {
map.remove(key);
}
composited[composited.length - 1].put(key, value);
return result;
}
类CompositeMap
我们来看一下对于这个类的声明:
private static class CompositeMap
显而易见,CompositeMap继承了Map类的属性以及方法,那么它就必须要自行实现所有接口所定义的方法,因此我们的Map实现类所实现的方法都会被我们重新定义,不能再直接调用了。这样做的目的是无需再冗余的去定义和Map接口一样的一个接口,二是还能防止预期之外的函数出现,以及给后续开发人员更好的理解程度。
我们注意到在这个类中有这么两个语句
MapcomparedMap = new HashMap<>(); Map identityMap = new IdentityHashMap<>();
我们已经和HashMap打过无数交道了,它是一个存储键值对的集合,而键值对中的键也就是key是绝对不能重复的,这也是它的特点之一。那么后面的IdentityHashMap又是怎么样的一个类呢?我们经过查阅资料可以得知:所谓的IdentityHsashMap,顾名思义,它允许"自己"相同的key保存进来,因此又一个相同二字。我们举例说明:
public static void main(String[] args) {
//IdentityHashMap使用===================================
Map identityHashMap = new IdentityHashMap<>();
identityHashMap.put(new String("a"), "1");
identityHashMap.put(new String("a"), "2");
identityHashMap.put(new String("a"), "3");
System.out.println(identityHashMap.size());
//这里的输出结果是3
Map identityHashMap2 = new IdentityHashMap<>();
identityHashMap2.put(new Demo(1), "1");
identityHashMap2.put(new Demo(1), "2");
identityHashMap2.put(new Demo(1), "3");
System.out.println(identityHashMap2.size());
//这里的输出结果是3
}
输出:
3 3
可以见得,它好像违背了Map的规则,把相同的key保存进去了。 是的,这就是它最大的特性之一。因此对应的,我们看看get方法结果:
System.out.println(identityHashMap.get("a"));
System.out.println(identityHashMap2.get(new Demo(1)));
输出:
null null
为什么会得到null呢?我们再来看一个例子就会明白了:
public static void main(String[] args) {
Demo demo1 = new Demo(1);
Demo demo2 = new Demo(1);
System.out.println(demo1 == demo2);
System.out.println(demo1.hashCode());
System.out.println(demo2.hashCode());
System.out.println(System.identityHashCode(demo1));
System.out.println(System.identityHashCode(demo2));
}
输出
false 1 1 832916738 1820524293
从这个例子中,我们能够得出结论:
”= =“比较的是地址值,而不是HashCode.
而我们的IdentityHashMap,比较key值,直接使用的是“= =”,因此上面例子出现的结果,我们自然而然的就能够理解了。那么我们再使用一个实例来验证我们的结论:
public static void main(String[] args) {
Demo demo1 = new Demo(1);
Demo demo2 = new Demo(1);
Map identityHashMap = new IdentityHashMap<>();
identityHashMap.put(demo1,"demo1");
identityHashMap.put(demo2,"demo2");
System.out.println(identityHashMap.get(demo1));
}
输出
demo1
至此,我们大致理解了IdentityHashMap类。
比如对于要保存的key,k1和k2,当且仅当k1== k2的时候,IdentityHashMap才会相等,而对于HashMap来说,相等的条件则是:对比两个key的hashCode等
IdentityHashMap不是Map的通用实现,它有意违反了Map的常规协定。并且IdentityHashMap允许key和value都为null。
同HashMap,IdentityHashMap也是无序的,并且该类不是线程安全的,如果要使之线程安全,可以调用Collections.synchronizedMap(new IdentityHashMap(…))方法来实现。
我们继续往下看。
构造类方法CompositeMap CompositeMap(Class>... comparedTypes) {
for (Class> class1 : comparedTypes) {
if (!Modifier.isFinal(class1.getModifiers())) {
throw new RuntimeException(class1 + " is not final...for this to reliably work; it should be");
}
}
typeCompared = Sets.newHashSet(comparedTypes);
}
我们先来看一下对于传进的参数comparedTypes,我们要做一个什么流程的初始化。方法的开头是一个for循环,首先是循环遍历了参数comparedTypes内的所有值,然后判断这个值是否满足如下条件:
if (!Modifier.isFinal(class1.getModifiers()))
我们来看一下这个Modifier.isFinal()是一个什么样的方法。要了解这个isFinal方法,我们得先清楚包含该方法的Modifier类究竟是一个什么类型的类。我们查阅资料后得知有如下信息:
Modifier 类 (修饰符工具类) 位于 java.lang.reflect 包中,用于判断和获取某个类、变量或方法的修饰符
Modifier 类将各个修饰符表示为相对应的整数,在源码中用 16 进制进行表示
而对于方法
Modifier.isFinal(int mod)
它的作用是判断整数参数是否包括 finale 修饰符,如果包含则返回 true,否则返回 false
好,那么我们再看后面的getModifiers方法是一个什么方法。我们先来看一下modifier的含义:修饰符。因此getModifiers 得到的就是前面的的修饰符 ,这个方法字段和方法都有。这个方法的值是修饰符相加的到的值。我们举个简单的例子:
public class Test1 {
String c;
public String a;
private String b;
protected String d;
static String e;
final String f="f";
}
Field[] fields = Test1.class.getDeclaredFields();
for( Field field: fields) {
System.out.println( field.getName() +":" + field.getModifiers() );
}
输出
c:0 a:1 b:2 d:4 e:8 f:16
这些数字是什么呢?我们看一下如下图片:
所以:什么都不加 是0 ,public是1 ,private 是2 ,protected是4,static是8 ,final是16。如果是 public static final 三个修饰的就是3个的加和为25 。
两个方法连起来的意思就为:取出修饰符对应的十进制数,然后判断是不是修饰符finale对应的十进制数,如果是就判断为true,但是由于在开头的!符号导致如果是是就判断为false,而不是则判断为true。
然后,我们看一下当满足源码中的条件后会执行什么语句:
throw new RuntimeException(class1 + " is not final...for this to reliably work; it should be");
很显然,这个语句的作用就是向上级抛出RuntimeException类型的异常,然后再在控制台输出语句。也就是说如果满足了源码中的条件,那么对于这个class变量就会初始化失败并向上级抛出错误和打印对应的语句。
最后,如果传入的参数内部数据准确无误符合标准,就会被传入到一个新的哈希集合,作为初始条件方便后续使用。
小结对于PlanMapper.java文件中的全局变量,以及自定义类,自定义类内部的初始化方法和内部全局变量我们已经全部解析完毕,学到了很多新的知识,认识到了新的类的作用以及用法,下一篇章将会开始解析重构后的CompositeMap方法。



