2021SC@SDUSC
上篇我们分析了Tuple(接口)和AbstractTuple(抽象类),这两个都是抽象方法,我们继续分析它的实现DefaultTuple
相关资料链接:【Pig源码分析】谈谈Pig的数据模型 -数据库-火龙果软件工程
类之间关系如下
从上图可以知道,在DefaultTuple类中有List<Object> mFields字段,这便是存储Tuple数据的地方了,mFields所持有类型为ArrayList<Object>()
DefaultTuple代码如下
函数功能:元组的默认实现。这个类将由DefaultTupleFactory创建
构造函数:
默认构造函数。这个构造函数是公共的,所以hadoop可以直接调用它。不过,inside pig不应该调用这个函数。使用TupleFactory代替。分配后,时间复杂度:O(1)
public DefaultTuple() {
mFields = new ArrayList<Object>();
}
构造一个具有已知字段数量的元组。包级别,以便调用者不能直接调用它。生成的元组被预填充为空元素。分配后,时间复杂度:O(N)@param大小 在元组中分配的字段数量
DefaultTuple(int size) {
mFields = new ArrayList<Object>(size);
for (int i = 0; i < size; i++)
mFields.add(null);
}
从现有的对象列表构造一个元组。包级别,以便调用者不能直接调用它。
加上分配后输入对象迭代的运行时间,时间复杂度:O(N)
@param c要转换为元组的对象列表
DefaultTuple(List<Object> c) {
mFields = new ArrayList<Object>(c);
}
从现有的对象列表构造一个元组。包级别,以便调用者不能直接调用它。
时间复杂度O(1) @param c要转换为元组的对象列表。这个列表将作为元组的一部分保存。@param junk只是用来区别于上面复制列表的构造函数
DefaultTuple(List<Object> c, int junk) {
mFields = c;
}
函数:
@Override
public int size() {
return mFields.size();
}
获取给定字段中的值。
public int size() {
return mFields.size();
}
以列表的形式获取元组中的所有字段。
@Override
public List<Object> getAll() {
return mFields;
}
在给定字段中设置值。
@Override
public void set(int fieldNum, Object val) throws ExecException {
mFields.set(fieldNum, val);
}
将字段附加到元组。这种方法效率不高,因为它可能会强制复制现有数据以实现增长数据结构。只要有可能,你就应该用newTuple(int)方法构造Tuple,然后填充在set()的值中,而不是用newTuple()构造它并附加值。
@Override
public void append(Object val) {
mFields.add(val);
}
确定元组在内存中的大小。这是数据包用来确定其内存大小的。这并不需要准确的,但它应该是一个体面的估计。
@Override
public long getMemorySize() {
Iterator<Object> i = mFields.iterator();
// mfields大小的其余固定部分在empty_tuple_size中计算
long mfields_var_size = SizeUtil.roundToEight(4 + 4 * mFields.size());
// 在java hotspot 32bit vm中,最小元组大小似乎是96,可能来自这个数组列表的最小大小
mfields_var_size = Math.max(40, mfields_var_size);
// fixed overhead = 48 bytes
//8 - tuple object header
//8 - mFields reference
//32 - mFields array list fixed size
long sum = 48 + mfields_var_size;
while (i.hasNext()) {
sum += SizeUtil.getPigObjMemSize(i.next());
}
return sum;
}
是否完全相等
@Override
public int compareTo(Object other)
笔记:前面这几个函数都是非常简单的后面的开始难了,这个有个被定义成static的函数,没有给出函数功能的注释,从名字上看是用于比较的?
public static class DefaultTupleRawComparator extends WritableComparator implements TupleRawComparator
继承自一个接口,一个类,引入部分如下,接口在同一个包
import org.apache.hadoop.io.WritableComparator;
hadoop源码项目没有给,TupleRawComparator如下
/**检查比较的元组中是否有一个有空字段。这个方法只有在
*{@link #compare(byte[],int,int,byte[],int,int)}返回的值为0(即元组被确定为0*/
public interface TupleRawComparator extends RawComparator,Configurable {
public boolean hasComparedTupleNull();
}
我们回来看DefaultTupleRawComparator内的函数
public DefaultTupleRawComparator() {
super(DefaultTuple.class);
}
@Override
public Configuration getConf() {
return null;
}
@Override
public void setConf(Configuration conf)
@Override
public boolean hasComparedTupleNull() {
return mHasNullField;
}
将两个defaulttuple比较为原始字节。我们假设元组不是PigNullableWritable,所以是客户端类需要处理Null和Index
@Override
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
ByteBuffer bb1 = ByteBuffer.wrap(b1, s1, l1);
ByteBuffer bb2 = ByteBuffer.wrap(b2, s2, l2);
int rc = compareDefaultTuple(bb1, bb2, true); // FIXME adjust for secondary sort asc
return rc;
}
将两个defaulttuple比较为原始字节
private int compareDefaultTuple(ByteBuffer bb1, ByteBuffer bb2, boolean topLevel)
这段程序很长,主要逻辑整理如下,首先比较大小相同,如果大小相同,逐个字段进行比较(这里需要case 每一种出现的类型,这里的全部类型是hadoop支持的全部类型)
功能和上面类似?是对父类的实现
@Override
public int compare(Object o1, Object o2) {
NullableTuple nt1 = (NullableTuple) o1;
NullableTuple nt2 = (NullableTuple) o2;
int rc = 0;
// if either are null, handle differently
if (!nt1.isNull() && !nt2.isNull()) {
rc = compareTuple((Tuple) nt1.getValueAsPigType(), (Tuple) nt2.getValueAsPigType());
} else {
// for sorting purposes two nulls are equal
if (nt1.isNull() && nt2.isNull())
rc = 0;
else if (nt1.isNull())
rc = -1;
else
rc = 1;
if (mWholeTuple && !mAsc[0])
rc *= -1;
}
return rc;
}
比较Tuple private int compareTuple(Tuple t1, Tuple t2) { int sz1 = t1.size(); int sz2 = t2.size(); if (sz2 < sz1) { return 1; } else if (sz2 > sz1) { return -1; } else { for (int i = 0; i < sz1; i++) { try { int c = DataType.compare(t1.get(i), t2.get(i)); if (c != 0) { if (!mWholeTuple && !mAsc[i]) c *= -1; else if (mWholeTuple && !mAsc[0]) c *= -1; return c; } } catch (ExecException e) { throw new RuntimeException("Unable to compare tuples", e); } } return 0; } } }
自此,DefaultTupleRawComparator程序结束了,功能为支持对父类的各种类型的比较功能(还有几个函数功能不清楚,没有父类源码)
计算hashCode的长度?推测功能和前面估计内存大小的函数差不多 @Override public int hashCode() { int hash = 17; for (Iterator<Object> it = mFields.iterator(); it.hasNext();) { Object o = it.next(); if (o != null) { hash = 31 * hash + o.hashCode(); } } return hash; }
输出tuple @Override public void write(DataOutput out) throws IOException { out.writeByte(DataType.TUPLE); int sz = size(); out.writeInt(sz); for (int i = 0; i < sz; i++) { DataReaderWriter.writeDatum(out, mFields.get(i)); } }
从输入流构造tuple,不过这个作者建议不用append这里又用了 @Override public void readFields(DataInput in) throws IOException { // Clear our fields, in case we're being reused. mFields.clear(); // Make sure it's a tuple. byte b = in.readByte(); if (b != DataType.TUPLE) { int errCode = 2112; String msg = "Unexpected data while reading tuple " + "from binary file."; throw new ExecException(msg, errCode, PigException.BUG); } // Read the number of fields int sz = in.readInt(); for (int i = 0; i < sz; i++) { try { append(DataReaderWriter.readDatum(in)); } catch (ExecException ee) { throw ee; } } }
返回类名 public static Class<? extends TupleRawComparator> getComparatorClass() { return DefaultTupleRawComparator.class; }
自此就是DefaultTuple全部内容了,整个程序给我的感觉就是:实际上存储的内容只有mFields,其他所有程序是构造、比较,以及为了实现可用hadoop的父类调用而去实现对hadoop中类型的支持。这样做的效果就是代码很冗长,但是支持性很好,如果有新的类型引入,直接新添加类或者继承这个类就行了,不需要修改之前写的代码。评价还是那句话,从项目层面可以便于拓展,便于协作,但是继承不得不实现父类未定义的方法即使用不上。
这里,我们终于将Pig的Data类型中的Tuple完全结束了,下一篇我们将研究Tuple的上一层,Bag