我查看了String.hashcode()方法的源代码。这是6-b14
中的实现,它已经被更改了。
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
我的问题是关于这一行的:
int len = count;
其中count
是全局变量,表示字符串的字符数。
为什么局部变量len
在这里用于循环条件,而不是全局变量本身?因为没有对变量进行操作,只有读取。如果使用全局字段进行读取或写入,那么使用局部变量是否是良好的实践?如果答案是肯定的,为什么阅读也是如此呢?
在String类中,我发现了一个关于String.trim()方法中对局部变量的奇怪赋值的注释,读为"avoid getfield opcode"
。
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[st] <= ' ')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
正如Frank Olschewski所指出的那样,整个事情似乎都是关于性能的。
在Java字节码中,实例变量实际上是由对象和名称引用的(使用GETFIELD
指令)。如果没有优化,VM必须做更多的工作来访问变量。
代码的一个潜在的性能影响是,它在每次通过循环时使用相对昂贵的GETFIELD
指令。方法中的局部赋值消除了每次通过循环时对GETFIELD
的需要。
jit优化器可能会优化循环,但也可能不会,因此开发人员可能会选择手动执行的安全路径。
有一个关于避免getfield操作码的单独问题,它有详细信息
访问局部变量列表比访问实例变量更快。新的Java 8代码(参见Anubians的回答)也考虑到了这一点。这就是为什么它们使用局部变量h
进行哈希计算,而不直接访问实例变量this.hash
并创建局部指针char val[] = value;
的原因。但是考虑到这一点,我不知道为什么他们不在for循环中使用i < val.length;
或者更好的z = val.length; i < z;
,而是使用i < value.length;
。
如果count
可以修改,那么您需要一个局部变量。如果您正在进行多线程,那么您需要一个局部变量。创建一个局部变量是最安全的。然而,这并不是绝对必要的。
在这种情况下,这是多余的,因为字符串无论如何都是不可变的。count的值甚至不能改变
它没什么用,这就是为什么在Java 8中它看起来像这样:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
他们甚至不再有count了,他们使用value.length
,其中value
是一个final char数组。
他们正在做char val[] = value
,但这只是一个参考,严格来说是不必要的。
通过使用局部变量可能会有一些微妙的微增强,或者可能是为了可读性而做的,但这是不必要的(在我看来可读性较差)。