【软件工程实践】Hive研究-Blog6

本文详细分析了Apache Hive源码中PlanMapper类的相关方法,包括size、isEmpty、containsKey、containsValue、get、put、remove、putAll、entrySet、clear和keySet等,探讨了这些方法在处理comparedMap和identityMap时的逻辑,加深了对Hive查询优化器内部实现的理解。
摘要由CSDN通过智能技术生成

【软件工程实践】Hive研究-Blog6

2021SC@SDUSC

研究内容介绍

本人负责的是负责的是将查询块QB转换成逻辑查询计划(OP Tree)
如下的代码出自apaceh-hive-3.1.2-src/ql/src/java/org/apache/hadoop/hive/ql/plan中,也就是我的分析目标代码。本周的研究计划是解析PlanMapper.java文件源码。由于在Blog5中已经初步完成了全局变量以及内部类的全局变量,还有构造类方法的解析,本周的研究内容就会放在解析修改后的方法源码上,也就是重写的方法。

PlanMapper.java文件代码解析

我们首先附上整个java文件的源码。

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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;

/**
 * Enables to connect related objects to eachother.
 *
 * Most importantly it aids to connect Operators to OperatorStats and probably RelNodes.
 */
public class PlanMapper {

  Set<EquivGroup> groups = new HashSet<>();
  private Map<Object, EquivGroup> objectMap = new CompositeMap<>(OpTreeSignature.class);

  /**
   * Specialized class which can compare by identity or value; based on the key type.
   */
  private static class CompositeMap<K, V> implements Map<K, V> {

    Map<K, V> comparedMap = new HashMap<>();
    Map<K, V> identityMap = new IdentityHashMap<>();
    final Set<Class<?>> typeCompared;

    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);
    }

    @Override
    public int size() {
      return comparedMap.size() + identityMap.size();
    }

    @Override
    public boolean isEmpty() {
      return comparedMap.isEmpty() && identityMap.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
      return comparedMap.containsKey(key) || identityMap.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
      return comparedMap.containsValue(value) || identityMap.containsValue(value);
    }

    @Override
    public V get(Object key) {
      V v0 = comparedMap.get(key);
      if (v0 != null) {
        return v0;
      }
      return identityMap.get(key);
    }

    @Override
    public V put(K key, V value) {
      if (shouldCompare(key.getClass())) {
        return comparedMap.put(key, value);
      } else {
        return identityMap.put(key, value);
      }
    }

    @Override
    public V remove(Object key) {
      if (shouldCompare(key.getClass())) {
        return comparedMap.remove(key);
      } else {
        return identityMap.remove(key);
      }
    }

    private boolean shouldCompare(Class<?> key) {
      return typeCompared.contains(key);
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
      for (Entry<? extends K, ? extends V> e : m.entrySet()) {
        put(e.getKey(), e.getValue());
      }
    }

    @Override
    public void clear() {
      comparedMap.clear();
      identityMap.clear();
    }

    @Override
    public Set<K> keySet() {
      return Sets.union(comparedMap.keySet(), identityMap.keySet());
    }

    @Override
    public Collection<V> values() {
      throw new UnsupportedOperationException("This method is not supported");
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
      return Sets.union(comparedMap.entrySet(), identityMap.entrySet());
    }

  }

  /**
   * A set of objects which are representing the same thing.
   *
   * A Group may contain different kind of things which are connected by their purpose;
   * For example currently a group may contain the following objects:
   * <ul>
   *   <li> Operator(s) - which are doing the actual work;
   *   there might be more than one, since an optimization may replace an operator with a new one
   *   <li> Signature - to enable inter-plan look up of the same data
   *   <li> OperatorStats - collected runtime information
   * </ul>
   */
  public class EquivGroup {
    Set<Object> members = new HashSet<>();

    public void add(Object o) {
      if (members.contains(o)) {
        return;
      }
      members.add(o);
      objectMap.put(o, this);
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> getAll(Class<T> clazz) {
      List<T> ret = new ArrayList<>();
      for (Object m : members) {
        if (clazz.isInstance(m)) {
          ret.add((T) m);
        }
      }
      return ret;
    }
  }

  /**
   * States that the two objects are representing the same.
   *
   * For example if during an optimization Operator_A is replaced by a specialized Operator_A1;
   * then those two can be linked.
   */
  public void link(Object o1, Object o2) {

    Set<Object> keySet = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>());
    keySet.add(o1);
    keySet.add(o2);
    keySet.add(getKeyFor(o1));
    keySet.add(getKeyFor(o2));

    Set<EquivGroup> mGroups = Collections.newSetFromMap(new IdentityHashMap<EquivGroup, Boolean>());

    for (Object object : keySet) {
      EquivGroup group = objectMap.get(object);
      if (group != null) {
        mGroups.add(group);
      }
    }
    if (mGroups.size() > 1) {
      throw new RuntimeException("equivalence mapping violation");
    }
    EquivGroup targetGroup = mGroups.isEmpty() ? new EquivGroup() : mGroups.iterator().next();
    groups.add(targetGroup);
    targetGroup.add(o1);
    targetGroup.add(o2);

  }

  private OpTreeSignatureFactory signatureCache = OpTreeSignatureFactory.newCache();

  private Object getKeyFor(Object o) {
    if (o instanceof Operator) {
      Operator<?> operator = (Operator<?>) o;
      return signatureCache.getSignature(operator);
    }
    return o;
  }

  public <T> List<T> getAll(Class<T> clazz) {
    List<T> ret = new ArrayList<>();
    for (EquivGroup g : groups) {
      ret.addAll(g.getAll(clazz));
    }
    return ret;
  }

  public void runMapper(GroupTransformer mapper) {
    for (EquivGroup equivGroup : groups) {
      mapper.map(equivGroup);
    }
  }

  public <T> List<T> lookupAll(Class<T> clazz, Object key) {
    EquivGroup group = objectMap.get(key);
    if (group == null) {
      throw new NoSuchElementException(Objects.toString(key));
    }
    return group.getAll(clazz);
  }

  public <T> T lookup(Class<T> clazz, Object key) {
    List<T> all = lookupAll(clazz, key);
    if (all.size() != 1) {
      // FIXME: use a different exception type?
      throw new IllegalArgumentException("Expected match count is 1; but got:" + all);
    }
    return all.get(0);
  }

  @VisibleForTesting
  public Iterator<EquivGroup> iterateGroups() {
    return groups.iterator();

  }

  public OpTreeSignature getSignatureOf(Operator<?> op) {
    OpTreeSignature sig = signatureCache.getSignature(op);
    return sig;
  }

}


方法size

    @Override
    public int size() {
      return comparedMap.size() + identityMap.size();
    }

我们先来看看这个comparedMap和identityMap分别是一个什么样子的变量?我们联系上文,看到了comnparedMap以及identityMap分别是一个全局变量,而且它们的类型是Map类型。如下为其定义方法:

  Map<K, V> comparedMap = new HashMap<>();
  Map<K, V> identityMap = new IdentityHashMap<>();

那么,我们只用知道Map.size()方法具体是做什么作用的,就可以知道本方法的作用和意图了。我们查阅资料,得知size()方法用于计算 hashMap中键/值对的数量,size()方法的语法为:hashmap.size(),其中的这个hashmap是HashMap类的一个对象。方法size()的返回值是返回hashMap中键/值对的数量。我们可以通过一个实例来演示该方法的作用:

import java.util.HashMap;

class Main {
    public static void main(String[] args) {

        HashMap<Integer, String> sites = new HashMap<>();

        // 往 HashMap 添加一些元素
        sites.put(1, "Google");
        sites.put(2, "Runoob");
        sites.put(3, "Taobao");
        System.out.println("HashMap: " + sites);

        // 获得该 HashMap 中键/值对映射的数量
        int size = sites.size();
        System.out.println("Size of HashMap: " + size);
    }
}

输出

HashMap: {1=Google, 2=Runoob, 3=Taobao}
Size of HashMap: 3

至此,我们知道了size()方法的作用了。那么,我们回到源码中来,size方法的返回就是返回comparedMap的键值对数量和identityMap的键值对数量的二者之和,用于快速得到全局的键值对之和的方法。

方法isEmpty

    @Override
    public boolean isEmpty() {
      return comparedMap.isEmpty() && identityMap.isEmpty();
    }

这个方法非常简单,Map.isEmpty()方法即使判断这个Map集合是否包含内容,如果不包含任何内容,那就返回true,否则返回false。那么整个方法也就明白了:判断comparedMap以及identityMap这两个变量是否同时为空,如果是则返回true,其他情况则返回false。整个方法的作用为确定这两个关键变量是否同时为空,确保不会空调用而发生异常。

方法containsKey

    @Override
    public boolean containsKey(Object key) {
      return comparedMap.containsKey(key) || identityMap.containsKey(key);
    }

我们通过搜索资料得知,方法map.containsKey方法是用来判断Map集合对象中是否包含指定的键名,如果Map集合中包含这个指定的键名则返回true,否则返回false。其调用语法为containsKey(Object key),其中key为要查询的Map集合的键名对象。得知其用法后,我们就知道了整个方法的意图:查看comparedMap以及indentityMap中是否包含指定的键名,如果其中有任意一者包含有指定的键名则返回true,其他情况则返回false。

方法containsValue

    @Override
    public boolean containsKey(Object key) {
      return comparedMap.containsKey(key) || identityMap.containsKey(key);
    }

对于本方法,我们对比着刚才分析的containsKey方法来看,这个方法是用于判断目标Map中是否包含指定的Value值的,与containsKey方法类似。经过查阅资料,确定了我们的猜想是正确的。那么该方法的作用就是查询conparedMap以及identityMap中是否包含有指定的value值,只要二者中有任意一者的value集合中包含了指定的值,就会返回true,其他情况则返回false。

方法get

    @Override
    public V get(Object key) {
      V v0 = comparedMap.get(key);
      if (v0 != null) {
        return v0;
      }
      return identityMap.get(key);
    }

要理解这个方法,我们就得先理解map.get(key)方法。我们经过查阅资料,得知了map.get()方法的作用是返回指定键所映射的值,而且如果不包含该键的映射关系则返回null。调用语法为get(Object key))。那么整个方法的作用就知道了:从comparedMap中查询有没有指定的键,如果有则返回该键所对应的值,如果没有则返回null.而重写的get的方法只是指定了从comparedMap中获取值,没有从两个Map变量中分别获取。

方法put

    @Override
    public V put(K key, V value) {
      if (shouldCompare(key.getClass())) {
        return comparedMap.put(key, value);
      } else {
        return identityMap.put(key, value);
      }
    }

我们先来看一下这个shouldCompare是一个什么方法。通过浏览全局的源码发现,这个方法是下文的方法,故我们现在先解析这是一个什么方法。

方法shouldCompare

    private boolean shouldCompare(Class<?> key) {
      return typeCompared.contains(key);
    }

同样的,我们还是得看一下这个typeCompared是一个什么东西.我们在源码开头的部分找到了关于这个变量的定义语句:final Set<Class<?>> typeCompared;这是一个Set类型的变量,里面装的类型为Class<?>类型的变量.那么.这个Class<?>是什么东西呢?通过查阅资料得知"?"是表示不确定的java类型.那么我们就来关注一下Set.contains()是一个什么方法.通过查阅资料,我们得知Java 集合类中的 Set.contains() 方法判断 Set 集合是否包含指定的对象。该方法返回值为 boolean 类型,如果 Set 集合包含指定的对象,则返回 true,否则返回 false。其调用语法为contains(Object o),其中o为要进行查询的对象.为了更加直观的了解该方法,我们用一个例子来说明:

public static void main(String[] args){
    Set set = new HashSet();
    set.add(new Date());
    set.add("apple");
    set.add(new Socket());
    boolean contains = set.contains("apple");
    if(contains){
    System.out.println("Set集合包含字符串apple");
    }else{
    System.out.println("Set集合不包含字符串apple");
  }
}

输出

Set集合包含字符串apple

那么这个方法的作用就知道了:判断是否包含指定key,若包含则返回true否则返回false.

我们返回到put方法的源码中来.我们再来看看这个getClass()方法是一个什么方法.通过查阅资料得知:Object getClass() 方法用于获取对象的运行时对象的类。其调用语法为:object.getClass().其返回值为返回对象的类.我们直接用一个例子来看这个方法的作用:

import java.util.ArrayList;
 
class RunoobTest {
    public static void main(String[] args) {
 
    // getClass() with Object
    Object obj1 = new Object();
    System.out.println("obj1 的类为: " + obj1.getClass());
 
    // getClass() with String
    String obj2 = new String();
    System.out.println("obj2 的类为: " + obj2.getClass());
 
    // getClass() with ArrayList
    ArrayList<Integer> obj3 = new ArrayList<>();
    System.out.println("obj3 的类为: " + obj3.getClass());
    }
}

输出

obj1 的类为: class java.lang.Object
obj2 的类为: class java.lang.String
obj3 的类为: class java.util.ArrayList

我们再回到put方法的源码中来看一下if语句的判断条件:如果传入的key对象的类在全局变量typeCompared中,则判断为true,否则判断为false.而当条件为true或者false时,就会分别插入两个不同的Map类型的变量中来.我们来看看官方的put()方法是一个什么方法:Java 集合类中的 Map.put() 方法将获取 Map 集合的所有键名,并存放在一个 Set 集合对象中。简单来说就是插入一个键值对,且该键值是不能重复的,否则会失败.那么整个方法就清楚了,.如果条件为true则把键值对放进comparedMap中来,如果为false则放进identityMap中去.

方法remove

    @Override
    public V remove(Object key) {
      if (shouldCompare(key.getClass())) {
        return comparedMap.remove(key);
      } else {
        return identityMap.remove(key);
      }
    }

我们来看一下这个Map.remove()方法是什么方法.通过查阅资料,我们得知:remove() 方法用于删除hashMap 中指定键 key 对应的键值对(key-value)。而调用remove()方法的语法为:hashmap.remove(Object key, Object value);,其中hashmap 是 HashMap 类的一个对象,key - 键值,value(可以为空)- 键值对(key-value)中 key 对应的 value 值.

返回值:

1.如果指定 key,返回指定键 key 关联的值,如果指定的 key 映射值为 null 或者该 key 并不存在于该 HashMap 中,此方法将返回null。

2.如果指定了 key 和 value,删除成功返回 true,否则返回 false。

我们回到源码中来:
if的判断条件:如果传入的key对象的类在全局变量typeCompared中,则判断为true,否则判断为false.而判断为true就会删除comparedMap中的键值对,判断为false则会删除comparedMap中的键值对.

方法putAll

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
      for (Entry<? extends K, ? extends V> e : m.entrySet()) {
        put(e.getKey(), e.getValue());
      }
    }

我们先来关注这个看起来非常奇怪的写法:Map<? extends K, ? extends V> m,这是什么意思?我们经过查阅资料得知,该语句的意思是:map的key要是K类或者其子类的对象,value要是类V或者其子类的对象。也就是说,传入的参数不是随意的,而是要为K以及V类的或者分别为其子类的参数,否则传参不正确,避免操作出现违法。那么对于后面的for语句里面的内容,我们也能够理解其中<? extends K, ? extends V>的意思了。那么这个Entry是什么东西呢?我们查阅资料,得到了如下信息:Entry是键值对的一个具体对象。在Map类设计是,提供了一个嵌套接口(static修饰的接口):Entry。Entry将键值对的对应关系封装成了对象,即键值对对象,这样我们在遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。那后面的entrySet()又是一个什么方法?我们浏览下文发现这个方法已经被重写了。

方法entrySet

    @Override
    public Set<Entry<K, V>> entrySet() {
      return Sets.union(comparedMap.entrySet(), identityMap.entrySet());
    }

首先我们来看一下这个Sets.union()是一个什么函数。我们从网络上获取到如下信息:Sets.union()返回两个集合的不可修改的视图。返回的集合包含任一后备集中包含的所有元素。对返回的set进行迭代,首先对set1的所有元素进行迭代,然后依次对set2的每个元素进行迭代,这些迭代不包含在set1中。对于其调用格式为:

public static <E> 
  Sets.union(Set<? extends E> set1, 
          Set<? extends E> set2)

那么我们回到源码,再来看看这个entrySet()是一个什么函数。entrySet() 方法返回映射中包含的映射的 Set 视图。其调用语法为:

hashmap.entrySet()

返回此映射中包含的映射的Set视图。那么什么是视图呢?Set 视图的意思是 HashMap 中所有的键值对都被看作是一个 set 集合。我们可以看一下实际的用法,这会让我们一目了然:

import java.util.HashMap;

class Main {
    public static void main(String[] args) {
        // 创建一个 HashMap
        HashMap<Integer, String> sites = new HashMap<>();

        // 往 HashMap 添加一些元素
        sites.put(1, "Google");
        sites.put(2, "Runoob");
        sites.put(3, "Taobao");
        System.out.println("sites HashMap: " + sites);

        // 返回映射关系中 set view
        System.out.println("Set View: " + sites.entrySet());
    }
}

输出

sites HashMap: {1=Google, 2=Runoob, 3=Taobao}
Set View: [1=Google, 2=Runoob, 3=Taobao]

那么回到我们重写之后的entrySet()方法,该方法的意思就是返回comparedMap集合和identityMap集合的并集。.

好,我们再次回到putAll方法中来。这个方法的意思已经明白了:遍历出传入的m的Entry对象,然后添加这个Entry对象在compareMap以及identityMap的并集中。

方法clear

    @Override
    public void clear() {
      comparedMap.clear();
      identityMap.clear();
    }

clear方法的用途很简单,就是把集合中所有的键值对删除,清空集合。这里的clear方法就是把comparedMap和identityMap两个集合清空。

方法keySet

    @Override
    public Set<K> keySet() {
      return Sets.union(comparedMap.keySet(), identityMap.keySet());
    }

我们来看一下这个keySet()具体是一个什么样的方法:keySet()方法返回map中所有key值的列表。简单来说就是去除所有的key值,并作为结果返回。那么该方法也知道了其目的:取出comparedMap和identityMap的所有key值并做个并运算,然后作为一个Set类型的变量返回。

方法values

    @Override
    public Collection<V> values() {
      throw new UnsupportedOperationException("This method is not supported");
    }

显而易见,该方法就是抛出一个异常,然后打印出指定语句,没有特别需要注意的地方。

小结

通过本周的学习,我掌握了非常多的知识,希望下一周的源码学习能够继续给我带来新的有趣知识并让我学以致用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值