在Iceberg中,有一个名为BinPacking
的类,这个类在manifest文件和规划切分时使用频繁。
我们这一章来解读此类。
首先说明这个类的作用,顾名思义,这个类主要用来根据指定算法处理装箱问题,在并发或分布式处理方面起作用。如果各位同学经常刷算法题,应该会遇到一类叫装箱问题的题目,其中较为典型的一种是:把一定数量的物品放入容量相同的一些箱子中,使得每个箱子中的物品大小之和不超过箱子容量并使所用的箱子数目最少。BinPacking
类便是做类似的事情。
既然是装箱问题,那首先肯定要有一个箱(bin),在这里也一样,BinPacking有一个私有的静态内部泛型类,名为Bin<T>
,主要有三个成员变量:箱重量上限,箱内物品的集合,已装入物品的重量和。
有了箱子,我们来看一下这个装箱类是如何工作的,首先我们查看其构造函数:
public ListPacker(long targetWeight, int lookback, boolean largestBinFirst) {
this.targetWeight = targetWeight;
this.lookback = lookback;
this.largestBinFirst = largestBinFirst;
}
在前期主要有两个:targetWeight和lookback,largestBinFirst是后期添加的。
targetWeight是目标大小,lookback是指要考虑的箱数量。当添加超过箱数量的新bin时,最早的箱将被释放。
largestBinFirst是后期添加的参数,当此值为true时,最大的箱将被优先释放。
再来看一下其两个方法:
public List<List<T>> pack(Iterable<T> items, Function<T, Long> weightFunc) {
return ImmutableList.copyOf(new PackingIterable<>(items, targetWeight, lookback, weightFunc, largestBinFirst));
}
public List<List<T>> packEnd(List<T> items, Function<T, Long> weightFunc) {
return Lists.reverse(ImmutableList.copyOf(Iterables.transform(
new PackingIterable<>(Lists.reverse(items), targetWeight, lookback, weightFunc, largestBinFirst),
Lists::reverse)));
}
- items:要装箱的物品
- weightFunc:物品重量的计算函数,用于计算机每个物品的重量。
上述这两个方法便是主要使用的类了,其中pack
里面使用了Iterable
进行懒加载,可以有效降低系统的资源占用于网络传输,这在iceberg的源码中非常常见,不熟悉Iterable
的同学可以跳回到这个类的初始版本源码。
可以看到packEnd
相对于pack
而言,将items进行了反转,即将要装载入箱的条目进行了反转,其它部分是相同的。
这两个方法,同时使用了一个内部类:PackingIterable
,这个内部类是其功能实现的主要载体,我们来看一下。
public PackingIterable(Iterable<T> iterable, long targetWeight, int lookback,
Function<T, Long> weightFunc, boolean largestBinFirst) {
Preconditions.checkArgument(lookback > 0,
"Bin look-back size must be greater than 0: %s", lookback);
this.iterable = iterable;
this.targetWeight = targetWeight;
this.lookback = lookback;
this.weightFunc = weightFunc;
this.largestBinFirst = largestBinFirst;
}
解释一下方法签名函数:
- iterable:要遍历的箱内物品主体
- targetWeight:箱的大小
- lookback:箱的数量
- weightFunc:计算要放入箱内物品的重量的方法
- largestBinFirst:是否优先返回最重的箱子
我们知道,对于Iterable
而言,最重要的莫过于iterator
(即迭代器)了,这里当真正遍历数据进行运算时,使用的是如下方法iterator
:
@Override
public Iterator<List<T>> iterator() {
return new PackingIterator<>(iterable.iterator(), targetWeight, lookback, weightFunc, largestBinFirst);
}
可以看到,使用的是PackingIterator
,所以我们继续深入查看PackingIterator
吧。
PackingIterator
的构造函数与上述类似,如下:
private PackingIterator(Iterator<T> items, long targetWeight, int lookback,
Function<T, Long> weightFunc, boolean largestBinFirst) {
this.items = items;
this.targetWeight = targetWeight;
this.lookback = lookback;
this.weightFunc = weightFunc;
this.largestBinFirst = largestBinFirst;
}
**这个类中,最重要的是next
方法,源码就不贴出来了,它主要是在处理物品时分这么几步:
- 计算出物品的重量(
weightFunc
); - 找出可装填(
findBin
)当前物品——当前箱子总重量放入当前物品后不会超重——的箱子,如果没有可用的,则新建一个箱子; - 将物品放入箱内,如果箱子总量多出目标数量(
lookback
)时,返回最旧的(或最重的)箱子; - 当所有的条目都成功装箱后(已经没有需要装的物品),返回最旧的箱子。**
以上便是这个装箱类的主要处理逻辑了。
再次说明,如果有同学对遍历器不熟悉,可以去阅读初始版本的代码,这时就不贴出代码地址了。但最后依旧建议大家一定要对Iterator
了如指掌,因为在分布式程序中,它出现的概率很高,至少iceberg源码中便是如此。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。