说明
本文我们来介绍一下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;
}
方法如下:
-
next --> 返回与该条目名称相同的下一个条目,如果在该范围内未找到,则向外(即父scope)继续查找。方法如下:
public Entry next() { return shadowed; }
-
获得符合条件的下一个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); }
-
isStaticallyImported -->判断该entry是否是静态导入的.如下:
public boolean isStaticallyImported() { return false;
}
```
-
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]);
}
接下来是各种操作,我们来看一下:
-
dup 操作 --> 创建一个新的scope,其拥有同样的owner,新创建的scope和当前使用同一个table。
public Scope dup() { return dup(this.owner); }
-
dup 操作 在这个范围内构建一个新的范围,拥有新的拥有者,它与外部范围共享它的表。如果范围访问是堆栈式的,以避免分配新的表.
public Scope dup(Symbol newOwner) { Scope result = new Scope(this, newOwner, this.table, this.nelems); shared++; return result; }
-
dup 操作, 但是不共享table.
public Scope dupUnshared() { return new Scope(this, this.owner, this.table.clone(), this.nelems); }
-
删除当前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; }
-
扩容操作.
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; }
-
插入操作,将指定的Symbol插入到当前scope中.
public void enter(Symbol sym) { Assert.check(shared == 0); enter(sym, this); }
-
插入操作,将指定的Symbol插入到指定的scope中.
public void enter(Symbol sym, Scope s) { enter(sym, s, s, false); }
-
插入操作。将指定的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种情况:
-
symbol在当前scope中不存在,则此时需要创建一个Entry,其中Entry的shadowed指向哨兵, sibling指向当前的elems.创建完之后,在将elems 赋值为新创建的elems.如图:
可以从图中看出,新创建的Entry在链表中处于第一个,而sentinel则是在最后一个,这也就是为什么说elems是一个线性列表,它还包含所有出现在相反顺序的条目(即以后的条目被推到顶部) -
symbol所对应的hash值在当前的scope中已经有了,则此时处理的情况如下:
-
-
删除操作.
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,因此在删除时需要分别做处理
-
插入操作,如果不存在,才插入.
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); }
-
判断指定的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; }
-
过滤操作.
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; }
-
查找下标操作:
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 互素,才能使发生冲突的同义词地址均匀地分布在整个表中,否则可能造成同义词地址的循环计算。