javac中作用域的实现-scope

说明

本文我们来介绍一下javac中关于作业域的实现-Scope.该类表示Java程序中的可见性区域。Scope类是符号的容器,它提供了对给定名称的符号的有效访问。以哈希表的形式实现,具有“open addressing”和“double hashing”。作用域可以嵌套;作用域的下一个字段指向它的下一个外部范围。嵌套作用域可以共享它们的哈希表.

该类有以下的子类:

子类名功能
CompoundScope代表一个类作用域添加了跟踪相关类作用域变化的能力.这允许客户端直接(因为新成员已经添加/移除到此作用域)或间接(即,因为新成员已经添加/移除到父类的 scope中)了解类作用域是否已经改变。
DelegatedScope一个空的范围,不能放置任何东西。用于变量初始化器的作用域
ErrorScope代表一个错误的scope
ImportScope代表import 语句导入的scope
StarImportScope代表static import 语句导入的scope

接下来我们就来看一下这部分的实现

解析

Entry

既然Scope是一个哈希表,那么我们就来看一下hash表中的entry是如何实现的.该类的字段如下:

// 引用的symbol,当且仅当 this == sentinel 时 sym == null
public Symbol sym;

// 拥有同样hashcode的Entry,或者是哨兵
private Entry shadowed;

// 在同一个scope中的下一个Entry
public Entry sibling;

/** .
 *  当且仅当 this == sentinel 时 scope == null.
 *  当这个entry是在import scope中,那么这个scope就是这个entry所对应的scope(被导入的scope)
 */
public Scope scope;

构造器如下:

public Entry(Symbol sym, Entry shadowed, Entry sibling, Scope scope) {
            this.sym = sym;
            this.shadowed = shadowed;
            this.sibling = sibling;
            this.scope = scope;
}

方法如下:

  1. next --> 返回与该条目名称相同的下一个条目,如果在该范围内未找到,则向外(即父scope)继续查找。方法如下:

    public Entry next() {
        return shadowed;
    }	
    
  2. 获得符合条件的下一个Entry.方法如下:

    public Entry next(Filter<Symbol> sf) {
        if (shadowed.sym == null || sf.accepts(shadowed.sym)) return shadowed;
        else return shadowed.next(sf);
    }
    

    其中,Filter–>作为布尔判断的简单过滤器。如果提供的元素与筛选器匹配,方法accepts将返回true。如下:

    public interface Filter<T> {
    /**
      * 如果元素满足滤波器所施加的约束,则为true
     */
    boolean accepts(T t);
    }
    
  3. isStaticallyImported -->判断该entry是否是静态导入的.如下:

    public boolean isStaticallyImported() {
            return false;
    

}
```

  1. getOrigin–> origin仅用于import scope中。对于其他的scope entry来说,enclosing type 是可以用的.如下:

    public Scope getOrigin() {
            return scope;
    }
    

Scope

该类的字段如下:

// 分享当前Scope的hash table的数量
private int shared;

// 下一个包围着的Scope(与此范围可以共享一个哈希表)
public Scope next;

// 当前scope所对应的Symbol
public Symbol owner;

// hash 表
Entry[] table;

// hash code 的掩码,该值总是等于table.length - 1
int hashMask;

// 一个线性列表,它还包含所有出现在相反顺序的条目(即以后的条目被推到顶部)。
public Entry elems;

// 在当前作用域中的元素的数量,包括已经被删除的element--> 哨兵
int nelems = 0;

// 当保存在该scope中的ScopeListener,当该scope中有结构变化时会进行唤醒的Listener。
List<ScopeListener> listeners = List.nil();

// 用来标识没有找打在当前作用域,同时用来表示删除的元素
private static final Entry sentinel = new Entry(null, null, null, null);

// 表初始的大小,初始大小为16
private static final int INITIAL_SIZE = 0x10;

// 代表一个空的scope
public static final Scope emptyScope = new Scope(null, null, new Entry[]{});


static final Filter<Symbol> noFilter = new Filter<Symbol>() {
    public boolean accepts(Symbol s) {
        return true;
    }
};
    

其中ScopeListener如下:

public interface ScopeListener {
    public void symbolAdded(Symbol sym, Scope s);
    public void symbolRemoved(Symbol sym, Scope s);
}

Scope的构造器如下:

// table的长度必须是2的指数倍
private Scope(Scope next, Symbol owner, Entry[] table) {
    this.next = next;
    Assert.check(emptyScope == null || owner != null);
    this.owner = owner;
    this.table = table;
    this.hashMask = table.length - 1;
}

// 用于dup(复制)和dupUnshared(不共享的复制)的构造器
private Scope(Scope next, Symbol owner, Entry[] table, int nelems) {
    this(next, owner, table);
    this.nelems = nelems;
}

// 创建一个scope,指定的owner,作为新创建的scope的下一个,同时有一个新的table,其长度为16
public Scope(Symbol owner) {
    this(null, owner, new Entry[INITIAL_SIZE]);
}

接下来是各种操作,我们来看一下:

  1. dup 操作 --> 创建一个新的scope,其拥有同样的owner,新创建的scope和当前使用同一个table。

     public Scope dup() {
        return dup(this.owner);
    }
    
  2. dup 操作 在这个范围内构建一个新的范围,拥有新的拥有者,它与外部范围共享它的表。如果范围访问是堆栈式的,以避免分配新的表.

    public Scope dup(Symbol newOwner) {
        Scope result = new Scope(this, newOwner, this.table, this.nelems);
        shared++;
        return result;
    }
    
  3. dup 操作, 但是不共享table.

     public Scope dupUnshared() {
        return new Scope(this, this.owner, this.table.clone(), this.nelems);
    }
    
  4. 删除当前scope中的所有元素.

    public Scope leave() {
        Assert.check(shared == 0);
        if (table != next.table) return next; // 如果当前的table和下一个table不是共享的,则直接返回下一个scope
        // 如果是共享的,则在table中删除本scope中的东西
        while (elems != null) {
            int hash = getIndex(elems.sym.name);
            Entry e = table[hash];
            Assert.check(e == elems, elems.sym);
            table[hash] = elems.shadowed;
            elems = elems.sibling;
        }
        Assert.check(next.shared > 0);
        next.shared--;
        next.nelems = nelems;
        // System.out.println("====> leaving scope " + this.hashCode() + " owned by " + this.owner + " to " + next.hashCode());
        // new Error().printStackTrace(System.out);
        return next;
    }
    
  5. 扩容操作.

    private void dble() {
        Assert.check(shared == 0);
        Entry[] oldtable = table;
        Entry[] newtable = new Entry[oldtable.length * 2];
        for (Scope s = this; s != null; s = s.next) {
            if (s.table == oldtable) {
                Assert.check(s == this || s.shared != 0);
                s.table = newtable;
                s.hashMask = newtable.length - 1;
            }
        }
        
        // 将原先scope中的元素加入到new table中
        int n = 0;
        for (int i = oldtable.length; --i >= 0; ) {
            Entry e = oldtable[i];
            if (e != null && e != sentinel) {
                table[getIndex(e.sym.name)] = e;
                n++;
            }
        }
        // We don't need to update nelems for shared inherited scopes,
        // since that gets handled by leave().
        nelems = n;
    }
    
  6. 插入操作,将指定的Symbol插入到当前scope中.

    public void enter(Symbol sym) {
        Assert.check(shared == 0);
        enter(sym, this);
    }
    
  7. 插入操作,将指定的Symbol插入到指定的scope中.

    public void enter(Symbol sym, Scope s) {
        enter(sym, s, s, false);
    }
    
  8. 插入操作。将指定的symbol插入到当前的scope.但是标识它是来自Scope s的,通过origin获取的.origin,staticallyImported 这两个参数是用在 import scope的

    public void enter(Symbol sym, Scope s, Scope origin, boolean staticallyImported) {
        Assert.check(shared == 0);
        if (nelems * 3 >= hashMask * 2) // 如果nelems * 3 >= hashMask * 2 则进行扩容一倍的处理
            dble();
        int hash = getIndex(sym.name); // 获取Symbol在当前scope中的操作
        Entry old = table[hash];
        if (old == null) {// 如果Symbol在当前scope中不存在,则其为sentinel,并增加nelems
            old = sentinel;
            nelems++;
        }
        // 创建Entry 添加到table中
        Entry e = makeEntry(sym, old, elems, s, origin, staticallyImported);
        table[hash] = e;
        elems = e;
    
        // 唤醒ScopeListener
        for (List<ScopeListener> l = listeners; l.nonEmpty(); l = l.tail) {
            l.head.symbolAdded(sym, this);
        }
    }
    
    Entry makeEntry(Symbol sym, Entry shadowed, Entry sibling, Scope scope, Scope origin, boolean staticallyImported) {
        return new Entry(sym, shadowed, sibling, scope);
    }
    

    那么此时有2种情况:

    1. symbol在当前scope中不存在,则此时需要创建一个Entry,其中Entry的shadowed指向哨兵, sibling指向当前的elems.创建完之后,在将elems 赋值为新创建的elems.如图:

      1
      可以从图中看出,新创建的Entry在链表中处于第一个,而sentinel则是在最后一个,这也就是为什么说elems是一个线性列表,它还包含所有出现在相反顺序的条目(即以后的条目被推到顶部)

    2. symbol所对应的hash值在当前的scope中已经有了,则此时处理的情况如下:

2

  1. 删除操作.

     public void remove(Symbol sym) {
        Assert.check(shared == 0);
        Entry e = lookup(sym.name); // 在当前scope中查找
        if (e.scope == null) return;// 如果e为sentinel,则不进行后续处理
    
        //  从表中和shadowed list 中删除e
        int i = getIndex(sym.name);
        Entry te = table[i];
        if (te == e)
            table[i] = e.shadowed;
        else while (true) {
            if (te.shadowed == e) {
                te.shadowed = e.shadowed;
                break;
            }
            te = te.shadowed;
        }
    
        // 从elems中和sibling list中删除e
        te = elems;
        if (te == e)
            elems = e.sibling;
        else while (true) {
            if (te.sibling == e) {
                te.sibling = e.sibling;
                break;
            }
            te = te.sibling;
        }
    
        // 唤醒ScopeListener
        for (List<ScopeListener> l = listeners; l.nonEmpty(); l = l.tail) {
            l.head.symbolRemoved(sym, this);
        }
    }
    

    从前面的两个图中可以看出, Entry 维护着shadowed和sibling,因此在删除时需要分别做处理

  2. 插入操作,如果不存在,才插入.

    public void enterIfAbsent(Symbol sym) {
        Assert.check(shared == 0);
        Entry e = lookup(sym.name);
        while (e.scope == this && e.sym.kind != sym.kind) e = e.next();
        if (e.scope != this) enter(sym);
    }
    
  3. 判断指定的symbol是否在当前scope中存在.

    public boolean includes(Symbol c) {
        for (Scope.Entry e = lookup(c.name);
             e.scope == this;
             e = e.next()) {
            if (e.sym == c) return true;
        }
        return false;
    }
    
  4. 过滤操作.

    public Entry lookup(Name name) {
        return lookup(name, noFilter);
    }
    
    public Entry lookup(Name name, Filter<Symbol> sf) {
        Entry e = table[getIndex(name)];
        if (e == null || e == sentinel) // 如果Name在当前scope中不存在的话,或者e是sentinel,则返回sentinel
            return sentinel;
        while (e.scope != null && (e.sym.name != name || !sf.accepts(e.sym)))
            e = e.shadowed;
        return e;
    }
    
  5. 查找下标操作:

    int getIndex (Name name) {
        int h = name.hashCode();
        int i = h & hashMask;
        // The expression below is always odd, so it is guaranteed
        // to be mutually prime with table.length, a power of 2. 下面的表达式总是奇数,因为保证它与表的长度(2的幂)相互素数。
        // 双重散列法(Double Hashing) 必须使 h1(key) 的值和 m 互素,才能使发生冲突的同义词地址均匀地分布在整个表中,否则可能造成同义词地址的循环计算。
        int x = hashMask - ((h + (h >> 16)) << 1);
        int d = -1; // Index of a deleted item. 已删除项目的索引
        for (;;) {
            Entry e = table[i];
            if (e == null)
                return d >= 0 ? d : i;
            if (e == sentinel) {
                // We have to keep searching even if we see a deleted item.
                // However, remember the index in case we fail to find the name. 我们必须继续搜索,即使我们看到一个被删除的项目。但是,如果我们找不到名称,请记住索引
                if (d < 0)
                    d = i;
            } else if (e.sym.name == name)
                return i;
            i = (i + x) & hashMask;
        }
    }
    

    那么什么是Double Hashing呢? 百度中的解释是这样的:

    该方法是开放地址法中最好的方法之一,它的探查序列是:
    hi=(h(key)+i*h1(key))%m 0≤i≤m-1 //即di=i*h1(key)
    即探查序列为:d=h(key),(d+h1(key))%m,(d+2h1(key))%m,…,等。
    该方法使用了两个散列函数h(key)和h1(key),故也称为双散列函数探查法。
    定义 h1(key) 的方法较多,但无论采用什么方法定义,都必须使 h1(key) 的值和 m 互素,才能使发生冲突的同义词地址均匀地分布在整个表中,否则可能造成同义词地址的循环计算。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值