前言

Presto的Slice并不在Presto包中,是在一个独立的包org.airlift.slice中org.airlift是个工具类,作者也是Presto的主要开发者,主要是服务于Presto的,但是我们也可以单独取出来用。

ClassLayout和Unsafe

在Java中一般是无法取得类的大小的,需要通过一些特殊的手段,例如Unsafe包中的方法。org.openjdk.jol包封装了很多Unsafe的方法。我们可以通过ClassLayout类来或者我们创建的Java对象在内存中的大小。
通过Unsafe我们可以对对象的内存直接进行操作。假设我们创建一个int类型的数组,每个元素的值是他Index的位置

  1. int[] nums = new int[20];
  2. for (int i = 0; i < nums.length; i++) {
  3. nums[i] = i;
  4. }

上面这种写法是常规的写法。我们也可以用Unsafe。

  1. /**
  2. * 调用反射获得Unsafe实例
  3. */
  4. Field field = Unsafe.class.getDeclaredField("theUnsafe");
  5. field.setAccessible(true);
  6. Unsafe unsafe = (Unsafe) field.get(null);
  7. int[] nums = new int[20];
  8. for (int i = 0; i < nums.length; i++) {
  9. unsafe.putInt(nums, (long) i * Unsafe.ARRAY_INT_INDEX_SCALE + Unsafe.ARRAY_INT_BASE_OFFSET, i);
  10. }

Unsafe实例我们需要通过反射获得,直接获得会抛出异常。ARRAY_INT_BASE_OFFSET表示数组对象的第一个元素在内存中的位置。ARRAY_INT_INDEX_SCALE表示每一个真正的元素的数据中的占据空间。这些变量都在Unsafe中,还有许多,对应byte数组,long数组等。
同样的,只要能获得对象的地址,那么我们就可以对任意的对象进行写入。我们尝试在Object对象中写入两个Long元素

  1. /**
  2. * 调用反射获得Unsafe实例
  3. */
  4. Field field = Unsafe.class.getDeclaredField("theUnsafe");
  5. field.setAccessible(true);
  6. Unsafe unsafe = (Unsafe) field.get(null);
  7. Object object = new Object();
  8. System.out.println(object);
  9. int size = ClassLayout.parseClass(Object.class).instanceSize();
  10. System.out.println("size: " + size); // 16
  11. /**
  12. * 把Object写为两个long
  13. */
  14. unsafe.putLong(object, 0L, 13L);
  15. unsafe.putLong(object, (long) SizeOf.SIZE_OF_LONG, 19L);
  16. System.out.println(unsafe.getLong(object, 0L)); // 13
  17. System.out.println(unsafe.getLong(object, (long)SizeOf.SIZE_OF_LONG)); // 19
  18. System.out.println(object); // upe

需要注意的是,这样Object对象就是不可用了。如果我们强行输出的话,会抛出空指针异常。
同样的,我们可以对对象进行设值,这个时候我们可以借助ClassLayout封装好的方法

  1. static class Person {
  2. private String name;
  3. private int age;
  4. //get set...
  5. @Override
  6. public String toString() {
  7. final StringBuilder sb = new StringBuilder("Person{");
  8. sb.append("name='").append(name).append('\'');
  9. sb.append(", age=").append(age);
  10. sb.append('}');
  11. return sb.toString();
  12. }
  13. }
  1. Person person = new Person();
  2. ClassLayout personClassLayout = ClassLayout.parseClass(Person.class);
  3. int size = personClassLayout.instanceSize();
  4. System.out.println("size: " + size);
  5. System.out.println("header size: " + personClassLayout.headerSize());
  6. /**
  7. * 得到所有的field的信息
  8. */
  9. SortedSet<FieldLayout> fields = personClassLayout.fields();
  10. for (FieldLayout layout : fields) {
  11. switch (layout.name()) {
  12. case "name":
  13. unsafe.putObject(person, ((long) layout.offset()), "Zhu");
  14. break;
  15. case "age":
  16. unsafe.putInt(person, ((long) layout.offset()), 20);
  17. break;
  18. default:
  19. break;
  20. }
  21. }
  22. System.out.println(person); // Person{name='Zhu', age=20}

总之,这就给了一个类似于利用大对象的内存配合Unsafe直接操作内存的方法做一个内存池的思路

Slices

我们无法直接创建Slice类,可以通过Slices类提供的很多的静态方法来进行创建。

  1. Slice allocate(int capacity);
  2. //利用这个方法我们可以直接创建一个容量为capacity的Slice,底层就是创建了一个byte[capacity]的数组,不过这个对象是在堆内的
  3. Slice allocateDirect(int capacity);
  4. //利用这个方法我们可以在堆外创建一块内存,底层是使用的nio的ByteBuffer.allocateDirect

创建了Slice之后,就可以往里面添加元素了。在Presto中最重要的两个用法就是FixedWidthBlock和VariableWidthBlock了。创建者两种Block运用他的Builder类FixedWidthBlockBuilder和VariableWidthBlockBuilder类。

FixedWidthBlockBuilder

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

VariableWidthBlockBuilder

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