前言
Presto的Slice并不在Presto包中,是在一个独立的包org.airlift.slice中org.airlift是个工具类,作者也是Presto的主要开发者,主要是服务于Presto的,但是我们也可以单独取出来用。
ClassLayout和Unsafe
在Java中一般是无法取得类的大小的,需要通过一些特殊的手段,例如Unsafe包中的方法。org.openjdk.jol包封装了很多Unsafe的方法。我们可以通过ClassLayout类来或者我们创建的Java对象在内存中的大小。
通过Unsafe我们可以对对象的内存直接进行操作。假设我们创建一个int类型的数组,每个元素的值是他Index的位置
int[] nums = new int[20];for (int i = 0; i < nums.length; i++) {nums[i] = i;}
上面这种写法是常规的写法。我们也可以用Unsafe。
/*** 调用反射获得Unsafe实例*/Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);Unsafe unsafe = (Unsafe) field.get(null);int[] nums = new int[20];for (int i = 0; i < nums.length; i++) {unsafe.putInt(nums, (long) i * Unsafe.ARRAY_INT_INDEX_SCALE + Unsafe.ARRAY_INT_BASE_OFFSET, i);}
Unsafe实例我们需要通过反射获得,直接获得会抛出异常。ARRAY_INT_BASE_OFFSET表示数组对象的第一个元素在内存中的位置。ARRAY_INT_INDEX_SCALE表示每一个真正的元素的数据中的占据空间。这些变量都在Unsafe中,还有许多,对应byte数组,long数组等。
同样的,只要能获得对象的地址,那么我们就可以对任意的对象进行写入。我们尝试在Object对象中写入两个Long元素
/*** 调用反射获得Unsafe实例*/Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);Unsafe unsafe = (Unsafe) field.get(null);Object object = new Object();System.out.println(object);int size = ClassLayout.parseClass(Object.class).instanceSize();System.out.println("size: " + size); // 16/*** 把Object写为两个long*/unsafe.putLong(object, 0L, 13L);unsafe.putLong(object, (long) SizeOf.SIZE_OF_LONG, 19L);System.out.println(unsafe.getLong(object, 0L)); // 13System.out.println(unsafe.getLong(object, (long)SizeOf.SIZE_OF_LONG)); // 19System.out.println(object); // upe
需要注意的是,这样Object对象就是不可用了。如果我们强行输出的话,会抛出空指针异常。
同样的,我们可以对对象进行设值,这个时候我们可以借助ClassLayout封装好的方法
static class Person {private String name;private int age;//get set...@Overridepublic String toString() {final StringBuilder sb = new StringBuilder("Person{");sb.append("name='").append(name).append('\'');sb.append(", age=").append(age);sb.append('}');return sb.toString();}}
Person person = new Person();ClassLayout personClassLayout = ClassLayout.parseClass(Person.class);int size = personClassLayout.instanceSize();System.out.println("size: " + size);System.out.println("header size: " + personClassLayout.headerSize());/*** 得到所有的field的信息*/SortedSet<FieldLayout> fields = personClassLayout.fields();for (FieldLayout layout : fields) {switch (layout.name()) {case "name":unsafe.putObject(person, ((long) layout.offset()), "Zhu");break;case "age":unsafe.putInt(person, ((long) layout.offset()), 20);break;default:break;}}System.out.println(person); // Person{name='Zhu', age=20}
总之,这就给了一个类似于利用大对象的内存配合Unsafe直接操作内存的方法做一个内存池的思路
Slices
我们无法直接创建Slice类,可以通过Slices类提供的很多的静态方法来进行创建。
Slice allocate(int capacity);//利用这个方法我们可以直接创建一个容量为capacity的Slice,底层就是创建了一个byte[capacity]的数组,不过这个对象是在堆内的Slice allocateDirect(int capacity);//利用这个方法我们可以在堆外创建一块内存,底层是使用的nio的ByteBuffer.allocateDirect
创建了Slice之后,就可以往里面添加元素了。在Presto中最重要的两个用法就是FixedWidthBlock和VariableWidthBlock了。创建者两种Block运用他的Builder类FixedWidthBlockBuilder和VariableWidthBlockBuilder类。
FixedWidthBlockBuilder

定长的Block,所以会固定一个FixedSize,然后底层就是一个byte数组。不管我们往里面写什么,只要一个entry的长度是FixedSize就行。同时这个不提供自动扩展内存的功能,当超出大小时,会抛出异常。
VariableWidthBlockBuilder

变长的Block,没有固定的大小,所以需要一个额外的数组记录指定entry的位置同时在每次增加之前会确保内存空间足够,如果不够会进行自动扩容。
