static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
该方法的作用是寻找指定数组在内存中i位置的数据。
下面分析下原理。
首先解释下每个参数的含义。
U:Unsafe,它的作用是提供Java直接操作内存的方法
i:索引
ASHIFT:
ABASE:起始位置
下面是涉及到的3个方法
// 获取obj对象中offset偏移地址对应的object型field的值,支持volatile load语义。
public native Object getObjectVolatile(Object obj, long offset);
//获取数组中第一个元素的偏移量(get offset of a first element in the array)
public native int arrayBaseOffset(java.lang.Class aClass);
//获取数组中一个元素的大小(get size of an element in the array)
public native int arrayIndexScale(java.lang.Class aClass);
贴出关键性代码
Class<?> ak = Node[].class;
// 获取数组的起始位置
ABASE = U.arrayBaseOffset(ak);
// 获取数组的数据的大小,比如byte类型的数据为1,int类型的为4,long为8
int scale = U.arrayIndexScale(ak);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
// Integer.numberOfLeadingZeros(scale) 获得指定数字前面的0的个数
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
现在要提出一个问题,数组是如何是实现随机访问的?
通过寻址地址。
寻址地址的计算公式:
a[i]_address = base_address + i * data_type_size
base_address :起始地址
i:索引
data_type_size :数据大小,根据数据类型byte,boolean长度为1,char,short长度为2,float,int长度为3,long,double长度为4
我们可以用int数组来分析上面的关键代码
Class ak = int[].class;
// 起始位置:16,
// TODO 不明白为什么是16,所有的基本类型,还有Object,String,都是16,不清楚
// 如果有清楚的,麻烦回复到评论
int ABASE = unsafe.arrayBaseOffset(ak);
System.out.println("a=" + ABASE);
// 数据长度:4,int的长度为4
int scale = unsafe.arrayIndexScale(ak);
long ASHIFT;
// 2 = 31 - 29(4转化为二进制为100,由于是32位的,所以前面有29位0补位)
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
System.out.println("ASHIFT=" + ASHIFT);
结合tabAt方法,i << 2 + ABASE
等价于 i * 4 + ABASE
符合寻址地址公式。
所以tabAt的作用就是寻找指定数组在内存中i位置的数据。
参考:
[1]: https://blog.csdn.net/fenglibing/article/details/17119959
[2]: https://www.cnblogs.com/mickole/articles/3757278.html